diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..a18db9c
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,42 @@
+# GPU Auto Optimizer
+
+Instructions for AI coding agents working in this repository. A native C++20 Windows tool that tunes
+NVIDIA GPUs (power limit, core and memory offsets) through NVML and NVAPI, with a DX11 stress test.
+User-facing overview: `README.md`. Code layout, profiles and release process: `docs/development.md`.
+
+## Hard requirement: English only
+
+Everything in this repository is in English, without exception: code, identifiers, comments,
+CLI output and log lines, commit messages, branch names, PR titles and descriptions, specs,
+plans, README, `docs/`, test names and file names. This holds even when the conversation with
+the user is in another language — translate, never paste non-English text into the project.
+
+## Build and test
+
+`cmake` is not on `PATH`; use the one bundled with Visual Studio:
+
+```powershell
+$bin = "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin"
+& "$bin\cmake.exe" --workflow --preset ci # configure, build Release, test
+& "$bin\cmake.exe" --build --preset debug; & "$bin\ctest.exe" --preset debug
+```
+
+Everything we compile is `/W4 /WX`; fix warnings, do not suppress them.
+
+## Layout
+
+Two executables on one engine: `gao.exe` (CLI, `src/app/main.cpp`) and
+`GpuAutoOptimizer.exe` (window + tray + watchdog, `src/app/gui/`). Logic both
+need lives in `src/app/common.*`, never in either entry point.
+
+## Rules the design depends on
+
+- `src/core/` is pure logic: no `windows.h`, NVML, NVAPI or D3D headers. It reaches hardware
+ only through `GpuControl` and callbacks, so CI tests it without a GPU.
+- Every hardware write is verified by reading it back; a mismatch is a failure. Any failed
+ apply ends at stock.
+- The crash journal's `begin` line is flushed to disk before a clock candidate touches the
+ hardware.
+- `src/hw/` is verified by hand on the reference RTX 4070 via `docs/hardware-checks.md`; record
+ the results there.
+- Commands that write to the GPU need an elevated shell.
diff --git a/CLAUDE.md b/CLAUDE.md
index e82a138..43c994c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,38 +1 @@
-# GPU Auto Optimizer
-
-## Hard requirement: English only
-
-Everything in this repository is in English, without exception: code, identifiers, comments,
-CLI output and log lines, commit messages, branch names, PR titles and descriptions, specs,
-plans, README, `docs/`, test names and file names. This holds even when the conversation with
-the user is in another language — translate, never paste non-English text into the project.
-
-## Build and test
-
-`cmake` is not on `PATH`; use the one bundled with Visual Studio:
-
-```powershell
-$bin = "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin"
-& "$bin\cmake.exe" --workflow --preset ci # configure, build Release, test
-& "$bin\cmake.exe" --build --preset debug; & "$bin\ctest.exe" --preset debug
-```
-
-Everything we compile is `/W4 /WX`; fix warnings, do not suppress them.
-
-## Layout
-
-Two executables on one engine: `gao.exe` (CLI, `src/app/main.cpp`) and
-`GpuAutoOptimizer.exe` (window + tray + watchdog, `src/app/gui/`). Logic both
-need lives in `src/app/common.*`, never in either entry point.
-
-## Rules the design depends on
-
-- `src/core/` is pure logic: no `windows.h`, NVML, NVAPI or D3D headers. It reaches hardware
- only through `GpuControl` and callbacks, so CI tests it without a GPU.
-- Every hardware write is verified by reading it back; a mismatch is a failure. Any failed
- apply ends at stock.
-- The crash journal's `begin` line is flushed to disk before a clock candidate touches the
- hardware.
-- `src/hw/` is verified by hand on the reference RTX 4070 via `docs/hardware-checks.md`; record
- the results there.
-- Commands that write to the GPU need an elevated shell.
+@AGENTS.md
diff --git a/README.md b/README.md
index 1912058..47d0110 100644
--- a/README.md
+++ b/README.md
@@ -1,140 +1,152 @@
+

+
# GPU Auto Optimizer
+**Finds the fastest settings your NVIDIA graphics card is stable at, and keeps them applied.**
+
+[](https://github.com/Rovey/gpu-auto-optimizer/releases/latest)
[](https://github.com/Rovey/gpu-auto-optimizer/actions/workflows/ci.yml)
[](LICENSE)
-
+

-**A native C++ NVIDIA GPU tuning tool, currently mid-rewrite.**
-
-
-
-> [!IMPORTANT]
-> `main` is being rewritten from Python to C++ (see [the design spec](docs/superpowers/specs/2026-09-20-cpp-rewrite-design.md)). What's here today is the repository skeleton, the hardware layer and the stress load: it can read live telemetry, write clock offsets and fan speed verified against the driver, and run a DX11 load that checks every value it computes and ends in a stability verdict. It can also tune: `--optimize` searches power limit, core and memory offsets against that verdict, saves the result, and `--boot on` re-applies it at every logon. `GpuAutoOptimizer.exe` is the window and tray app on top of the same engine; its tray process keeps the tune applied for the whole session. v1.0 will be the finished C++ application. The previous, working Python version (search engine, risk profiles, tray GUI) is preserved at the `v0.9-python` git tag and still works as it always did; it just isn't what lives on `main` anymore.
-
-> [!WARNING]
-> The commands below that write to the GPU (`--set-core`, `--set-mem`, `--set-fan`, `--reset`, `--optimize`, `--apply`, `--boot`) change clocks, power limit and fan behavior directly through the driver. Every write is verified by reading the value back rather than trusting the driver's return code, but a wrong offset can still make a card unstable. Use at your own risk.
+[**Download**](https://github.com/Rovey/gpu-auto-optimizer/releases/latest) · [How it works](#how-it-works) · [Command line](#command-line) · [FAQ](#faq)
-## Quick start
+
-### From a release
+
-Download `GpuAutoOptimizer--win-x64.zip` from [Releases](https://github.com/Rovey/gpu-auto-optimizer/releases), check it and unzip it anywhere:
+## What it does
-```powershell
-(Get-FileHash .\GpuAutoOptimizer--win-x64.zip -Algorithm SHA256).Hash # compare with the .sha256 file
-gh attestation verify .\GpuAutoOptimizer--win-x64.zip --repo Rovey/gpu-auto-optimizer # optional: proves CI built it from this repo
-```
+GPU Auto Optimizer tunes the **power limit** and the **core and memory clock offsets** of an NVIDIA card. It stress-tests every candidate setting and keeps the highest one that computes correct results, then backs off by a safety margin. You pick a goal and press one button; a run takes about ten minutes.
-The executables are not code-signed, so Windows SmartScreen may say it "protected your PC" on the first start: choose **More info**, then **Run anyway**. Turning on apply-at-logon copies both executables to `%ProgramFiles%\GpuAutoOptimizer\`, so the unzipped folder can be deleted afterwards.
+- **Four profiles:** Best of my GPU, Cool & efficient, Quiet and Max performance.
+- **Verified, not trusted:** every value written to the driver is read back and checked, and a failed apply ends at stock.
+- **Crash-proof search:** a journal on disk records each candidate before it is tried, so a setting that froze the machine is never tried again.
+- **Stays applied:** the tray app re-applies the tune at every logon, and within about half a minute after a driver reset (TDR).
+- **Plays fair:** if MSI Afterburner or another tool changes the settings, it steps aside instead of fighting over them.
+- **Native and small:** two C++ programs, under 2 MB together, with no installer, no runtime and no driver or service.
-### From source
+## Quick start
-Requires an x64 Windows machine with Visual Studio 2026 (C++ and CMake workload), whose bundled CMake (4.2 or newer) knows the VS 2026 generator. From a Developer PowerShell, in the repo root:
+1. Download `GpuAutoOptimizer--win-x64.zip` from the [latest release](https://github.com/Rovey/gpu-auto-optimizer/releases/latest) and unzip it anywhere.
+2. Start **`GpuAutoOptimizer.exe`**.
+3. Open **Optimize**, pick a profile and press **Optimize GPU**. The app asks for administrator rights, because it changes clocks and power limits.
+4. When the run is done, turn on **Apply at logon** to keep the result after a restart.
-```powershell
-cmake --workflow --preset ci # configure, build Release, run the tests
+> [!NOTE]
+> The executables are not code-signed yet, so Windows SmartScreen may warn on the first start. Choose **More info**, then **Run anyway**. To check the download first:
+> ```powershell
+> (Get-FileHash .\GpuAutoOptimizer--win-x64.zip -Algorithm SHA256).Hash # compare with the .sha256 file
+> gh attestation verify .\GpuAutoOptimizer--win-x64.zip --repo Rovey/gpu-auto-optimizer
+> ```
+
+Turning on **Apply at logon** copies the app to `%ProgramFiles%\GpuAutoOptimizer\`, so the unzipped folder can be deleted afterwards.
+
+## Requirements
+
+| | |
+|---|---|
+| OS | Windows 10 or 11, 64-bit |
+| GPU | NVIDIA, Pascal (GTX 10-series) or newer, with a current driver |
+| Rights | Administrator to optimize or apply; reading telemetry does not need them |
+
+## Profiles
+
+| Profile | Goal | Temperature limit | Tunes |
+|---|---|---|---|
+| **Best of my GPU** (default) | Highest confirmed clocks with a safety margin, most power the card allows | 75 °C | power, core, memory |
+| **Cool & efficient** | Lowest power limit that costs under 2 % speed; clocks stay stock | 65 °C | power |
+| **Quiet** | Milder clocks and the lowest power limit that costs under 2 % speed | 80 °C | power, core, memory |
+| **Max performance** | Highest confirmed clocks minus one step, highest power limit | 83 °C | power, core, memory |
+
+The safety margin: the search finds the highest stable offset and applies a fraction of it (70 % for Best of my GPU), always at least one step below the edge.
+
+## How it works
+
+```mermaid
+flowchart LR
+ A[Baseline
30 s at stock] --> B[Power limit]
+ B --> C[Core offset
binary search]
+ C --> D[Memory offset
bandwidth peak]
+ D --> E[Confirm edges
30 s probes]
+ E --> F[Safety margin]
+ F --> G[Soak
60 s]
+ G --> H[Save profile]
```
-That builds two programs in `.\build\Release\`: `GpuAutoOptimizer.exe`, the window and tray app, and `gao.exe`, the command-line tool. Both use the same engine and the same saved state.
+- **Stress test.** A DirectX 11 compute load in which the GPU checks every value it computes against a known answer. Each probe ends in a verdict: `STABLE`, `WRONG RESULT`, `DEVICE LOST`, `TOO HOT` or `NO TELEMETRY`.
+- **Memory stops at the bandwidth peak,** not at the first error. GDDR6 and GDDR6X retry failed transfers, so an overclocked memory bus loses speed long before it returns a wrong result. The search measures bandwidth at each step and keeps the lowest offset within 1 % of the best.
+- **Crash journal.** Before a candidate touches the hardware, a `begin` line is flushed to disk. If the machine freezes, the unmatched `begin` becomes a ceiling the next run stays below.
+- **Apply at logon.** A scheduled task starts the app in the tray at logon, which applies the saved profile. It refuses when the driver version or the card changed since tuning, and it stops after three logons in a row that crashed within two minutes.
+- **Tune watchdog.** Every 30 seconds the tray app reads back what the driver reports. It re-applies after a reset, gives up if the tune is reset four times in an hour (a sign it is not stable), and backs off when another program changed the settings.
-### The app
+## Command line
-Start `GpuAutoOptimizer.exe`. The **Dashboard** shows the GPU with live telemetry, what is applied right now next to the saved tune (with **Apply saved tune**, the **Apply at logon** switch and **Revert to stock**), the four profiles and a log of what happened. On the **Optimize** page, pick a profile and press **Optimize GPU** (it offers to restart as administrator first, because tuning writes clocks and power limits). The run shows temperature, power and every probe as it happens; **Abort** restores stock. The results compare before and after and can turn on apply at every logon.
+`gao.exe` ships next to the app and drives the same engine. Commands that write to the GPU need an elevated shell.
-With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. Closing the window keeps the app in the tray; **Exit** in the tray menu quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again (window, tray menu or `gao --apply`). Only one optimize runs at a time, from the window or the command line.
+| Command | What it does |
+|---|---|
+| `gao --optimize best\|quiet\|cool\|max` | Runs the search; exit code 0 when saved, 2 when applied but not saved |
+| `gao --apply` | Re-applies the saved profile |
+| `gao --reset` | Returns to stock clocks and the default power limit |
+| `gao --boot on\|off` | Turns apply-at-logon on or off |
+| `gao --status` | Saved profile, what is applied now, apply-at-logon state |
+| `gao --probe` | Live telemetry: clocks, temperature, fan, power |
+| `gao --stress ` | Runs the stress test alone and prints its verdict; changes nothing |
+| `gao --bandwidth` | Measures the current memory bandwidth |
-### The command line
+Ctrl+C during `--optimize` restores stock.
-```powershell
-.\build\Release\gao.exe --version
-.\build\Release\gao.exe --probe
-.\build\Release\gao.exe --stress 60
-.\build\Release\gao.exe --bandwidth
-.\build\Release\gao.exe --optimize best # elevated
-.\build\Release\gao.exe --apply # elevated: re-apply the saved result
-.\build\Release\gao.exe --boot on # elevated: re-apply at every logon
-.\build\Release\gao.exe --status
-```
+## FAQ
-`--optimize` runs baseline → power → core → memory → 60 s soak (~10 minutes), leaves the result applied until reboot (exit code 0; 2 if it was applied but could not be saved), and keeps a crash journal so a setting that froze the machine is never tried again. Ctrl+C restores stock. The core and memory edges it finds are each re-checked with a 30 s probe before the safety margin is applied, and the memory search stops at the **bandwidth peak** rather than at the highest offset that does not produce errors: GDDR6X and GDDR6 retry failed transfers, so an excessive memory clock costs speed long before it produces a wrong result. `--bandwidth` prints the current memory bandwidth.
+
+Does it work alongside MSI Afterburner?
-All state (`gao.json`, the crash journal, `boot.log`) lives in `%ProgramData%\GpuAutoOptimizer`, which users can read but only administrators can write. `--boot on` copies both executables to `%ProgramFiles%\GpuAutoOptimizer\` and registers a logon task (`\GpuAutoOptimizer\BootApply`) that starts that copy of the app in the tray, which re-applies the saved profile and keeps it applied; `--status` says whether the installed copies match your build; if the machine crashes within 2 minutes of three logons in a row, or the NVIDIA driver version changes, boot-apply stops and `--status` / `boot.log` say why. A profile only applies to the card it was tuned on (NVML UUID).
+Yes, as long as only one of them applies settings. If Afterburner applies an offset while GPU Auto Optimizer keeps its tune applied, the watchdog notices, says so in a notification and leaves the settings alone.
+
-> [!NOTE]
-> The logon task runs with administrator rights, so everything it touches is admin-only: the exe in Program Files and the state folder in ProgramData (a folder someone else created there first, or a link in its place, is refused). Both locations come from the registry, not from environment variables. The CRT is linked statically and every non-system DLL (`d3d11`, `dxgi`, `d3dcompiler_47`) is delay-loaded from System32, so nothing placed next to the exe is ever loaded. Profile values outside the search range are refused.
+
+What happens after a driver update?
-`--stress` runs a DX11 compute load that checks every value it computes and ends with a verdict (`STABLE`, `WRONG RESULT`, `DEVICE LOST`, `TOO HOT`, `NO TELEMETRY`); it changes no settings and needs no elevation.
+The saved profile is tied to the driver version it was tuned on. After an update it is not applied, and the app asks you to optimize again, because a new driver can change what is stable.
+
-`--probe` prints live telemetry (core/memory clock, temperature, fan %, power) read through NVML; it needs no elevation. `gao` also has write commands -- `--set-core `, `--set-mem `, `--set-fan `, `--reset` -- that go through NVAPI and each verify the change by reading it back rather than trusting the call's return code. These need an **elevated** (administrator) shell, and their read-back has been verified on an RTX 4070; see [`docs/hardware-checks.md`](docs/hardware-checks.md) for the checklist this project runs manually and its current state. **Fan control does not work on this project's own reference RTX 4070**: the legacy NVAPI cooler API that `gao` calls (`NvAPI_GPU_GetCoolerSettings`) answers `NVAPI_NOT_SUPPORTED` on that card -- NVIDIA dropped it on Turing-and-later GPUs -- and `gao` reports that honestly instead of pretending the fan moved.
+
+What if a setting crashes my PC?
-### Requirements
+During a search, the crash journal makes sure that setting is never tried again, and the next run stays below it. At logon, three crashes in a row within two minutes of applying switch apply-at-logon off.
+
-- Windows 10/11, 64-bit
-- To build: Visual Studio 2026 with the C++ and CMake components (CMake 4.2 or newer)
-- An NVIDIA GPU with a recent driver, for `--probe` and the write commands (NVML/NVAPI are loaded at runtime; nothing in the build itself needs a GPU, and CI builds the full executable on a GPU-less runner)
+
+Why is the fan not tuned on my card?
-## Project structure
+NVIDIA's legacy fan API that the app uses is not supported on RTX 40-series cards. The app detects this and skips fan tuning instead of pretending it worked.
+
-```
-src/core/ pure logic -- no windows.h, no driver calls, no D3D. Unit-tested in CI.
- types.hpp Telemetry, FanCurve, GpuControl -- the hardware seam
- objectives.* presets as data (see below)
- config.* JSON config round-trip
- stress_math.* exact-float stress inputs and the CPU reference result
- stability.* the stress run loop and its verdict (stable / wrong result / TDR / too hot / no telemetry)
- journal.* write-ahead log of clock candidates; a freeze becomes a ceiling
- search.* the tuner: baseline, power, core, memory, soak
- boot.* when boot-apply may run (strikes, driver) and the verified profile apply
-src/hw/ the only code that touches hardware: NVML, NVAPI and D3D11.
- nvml.* telemetry (clocks, temp, fan %, power) via NVML
- nvapi.* clock offsets and fan control via NVAPI, verified by read-back
- gpu_control.* wires nvml/nvapi into the GpuControl struct core code uses
- stress.* DX11 compute stress load; the GPU checks every value it computes
- app_files.* gao.json (atomic writes), the crash journal and boot.log, all flushed to disk
- boot_task.* the logon task, via schtasks.exe
-src/app/ common.*: application logic both programs share (optimize, boot-apply, logon task)
- main.cpp gao.exe, the command line
- gui/ GpuAutoOptimizer.exe: window, tray, optimize worker thread, watchdog loop
-tests/ doctest, core only.
-third_party/ doctest, nlohmann/json, Dear ImGui -- vendored as source, no package manager.
-docs/hardware-checks.md the manual checklist for what CI can't test (the runner has no GPU)
-```
+
+Does it undervolt?
-## Presets
+No. Locking a voltage point froze the reference card during development, so no profile touches the voltage curve.
+
-`src/core/objectives.cpp` defines four presets as plain data (a thermal ceiling, a fan ceiling, how hard to push clocks, and which of core/memory/power tuning each one enables):
+
+Where does it keep its data?
-| Preset | max temp | max fan | perf push | core OC | mem OC | power |
-|---|---|---|---|---|---|---|
-| BestOfMyGpu (default) | 75 °C | 60% | 0.7 | on | on | on |
-| Quiet | 80 °C | 40% | 0.4 | on | on | on |
-| CoolAndEfficient | 65 °C | 70% | 0.3 | off | off | on |
-| MaxPerformance | 83 °C | 100% | 1.0 | on | on | on |
+In `%ProgramData%\GpuAutoOptimizer`: `gao.json` (the saved profile), `journal.jsonl` (the crash journal) and `boot.log`. Users can read the folder; only administrators can write it, because the logon task runs with administrator rights.
+
-Pick one with `--optimize best|quiet|cool|max`. Perf push is a safety margin: the search finds the highest stable offset and applies that fraction of it, always at least one step below. Quiet and CoolAndEfficient (perf push below 0.5) also look for the lowest power limit that costs less than 2 % score. Fan tuning is skipped on cards without fan control (including the reference RTX 4070). Undervolting is a separate opt-in flag, off in every preset above.
+## Building from source
-## Development
+Visual Studio 2026 with the C++ and CMake components. From a Developer PowerShell in the repository root:
```powershell
-cmake --preset default
-cmake --build --preset debug
-ctest --preset debug
-
-cmake --preset asan # AddressSanitizer build in build-asan/
-cmake --build --preset asan
-ctest --preset asan # needs the MSVC bin\Hostx64\x64 folder on PATH
+cmake --workflow --preset ci # configure, build Release, run the tests
```
-Our code builds at `/W4 /permissive-` with warnings as errors; `third_party/` is a system include. All of it is compiled with Control Flow Guard and SDL checks; both executables are linked CET-compatible and carry an application manifest.
-
-`core_tests` is the only test binary. It covers `src/core/` -- presets, config round-trip, and the `GpuControl` seam that lets tests drive fake hardware through lambdas -- and needs no GPU, which is why CI runs it on a GitHub-hosted `windows-2025` runner, once normally and once under AddressSanitizer. `src/hw/` (NVML, NVAPI) is verified manually on real hardware instead, through [`docs/hardware-checks.md`](docs/hardware-checks.md); CI still builds the full `gao.exe` so a link error in the hardware layer is caught there rather than on a developer's machine.
-
-## Status and roadmap
-
-Phases 0-5 of the rewrite are done: repository migration, the CMake/CI skeleton, the hardware layer (NVML telemetry and power limit, NVAPI clock offsets, NVAPI fan control), the DX11 stress load with its stability verdict, the search with its crash-safe journal, persistence with boot-apply, and the window and tray app with its tune watchdog. Phase 6 is under way: the executables carry an icon and version information, and a `v*` tag builds a draft release (zip, SHA-256, build provenance attestation); the first release is still to be tagged. See [the design spec](docs/superpowers/specs/2026-09-20-cpp-rewrite-design.md) for the full phase table and the reasoning behind each decision (why NVAPI + NVML directly, why the Afterburner route was abandoned, why undervolting ships opt-in).
+This produces `build\Release\GpuAutoOptimizer.exe` and `build\Release\gao.exe`. See [docs/development.md](docs/development.md) for the code layout, the test setup and the hardware checklist.
## License
diff --git a/docs/development.md b/docs/development.md
new file mode 100644
index 0000000..560d4eb
--- /dev/null
+++ b/docs/development.md
@@ -0,0 +1,81 @@
+# Development
+
+How the code is organised, how to build and test it, and the rules the design depends on. For using the app, see the [README](../README.md).
+
+## Build and test
+
+Visual Studio 2026 with the C++ and CMake components (CMake 4.2 or newer, which knows the VS 2026 generator). From a Developer PowerShell in the repository root:
+
+```powershell
+cmake --workflow --preset ci # configure, build Release, run the tests
+
+cmake --preset default
+cmake --build --preset debug
+ctest --preset debug
+
+cmake --preset asan # AddressSanitizer build in build-asan/
+cmake --build --preset asan
+ctest --preset asan # needs the MSVC bin\Hostx64\x64 folder on PATH
+```
+
+Our code builds at `/W4 /permissive-` with warnings as errors; `third_party/` is a system include. Everything is compiled with Control Flow Guard and SDL checks, and both executables are linked CET-compatible and carry an application manifest.
+
+## Layout
+
+```
+src/core/ pure logic: no windows.h, no driver calls, no D3D. Unit-tested in CI.
+ types.hpp Telemetry, GpuControl (the hardware seam), AppliedState
+ objectives.* the four profiles as data
+ config.* gao.json round-trip
+ stress_math.* exact-float stress inputs and the CPU reference result
+ stability.* the stress run loop and its verdict
+ journal.* write-ahead log of clock candidates; a freeze becomes a ceiling
+ search.* the tuner: baseline, power, core, memory, confirmation, soak
+ boot.* when a logon apply may run (strikes, driver, card) and the verified apply
+ watchdog.* the tray app's decisions: re-apply, give up, back off, driver changed
+ task_xml.* the logon task definition
+src/hw/ the only code that touches hardware or the OS state folders.
+ nvml.* telemetry and power limit via NVML
+ nvapi.* clock offsets and fan control via NVAPI, verified by read-back
+ gpu_control.* wires NVML and NVAPI into GpuControl
+ stress.* the DX11 compute load; the GPU checks every value it computes
+ app_files.* gao.json (atomic writes), the crash journal and boot.log, flushed to disk
+ boot_task.* the logon task and the Program Files copy
+src/app/
+ common.* logic both programs share: optimize, logon apply, apply-at-logon on/off
+ main.cpp gao.exe, the command line
+ gui/ GpuAutoOptimizer.exe: window, tray, optimize worker thread, watchdog loop
+tests/ doctest, core only
+third_party/ doctest, nlohmann/json, Dear ImGui: vendored as source, no package manager
+```
+
+## Profiles as data
+
+`src/core/objectives.cpp` defines each profile as a thermal ceiling, a fan ceiling, a perf push (the fraction of the highest stable offset that is applied) and which of core, memory and power tuning it enables:
+
+| Profile | `--optimize` | Max temp | Max fan | Perf push | Core | Memory | Power |
+|---|---|---|---|---|---|---|---|
+| BestOfMyGpu (default) | `best` | 75 °C | 60 % | 0.7 | on | on | on |
+| Quiet | `quiet` | 80 °C | 40 % | 0.4 | on | on | on |
+| CoolAndEfficient | `cool` | 65 °C | 70 % | 0.3 | off | off | on |
+| MaxPerformance | `max` | 83 °C | 100 % | 1.0 | on | on | on |
+
+The applied offset is always at least one step below the confirmed edge. Profiles with a perf push below 0.5 also look for the lowest power limit that costs less than 2 % score. Fan tuning is skipped on cards without fan control.
+
+## Rules the design depends on
+
+- `src/core/` is pure logic. It reaches hardware only through `GpuControl` and callbacks, so CI tests it without a GPU.
+- Every hardware write is verified by reading it back; a mismatch is a failure. Any failed apply ends at stock.
+- The crash journal's `begin` line is flushed to disk before a clock candidate touches the hardware.
+- Everything the elevated logon task touches is admin-only: the copy in `%ProgramFiles%` and the state folder in `%ProgramData%`. A state folder someone else created first, or a link in its place, is refused. Both locations come from the registry, not from environment variables.
+- The CRT is linked statically and every non-system DLL (`d3d11`, `dxgi`, `d3dcompiler_47`, `dwmapi`) is delay-loaded from System32, so nothing placed next to the executable is loaded.
+
+## Testing
+
+`core_tests` covers `src/core/` and runs in CI on a GitHub-hosted `windows-2025` runner, once normally and once under AddressSanitizer. CI also builds both executables, so a link error in the hardware layer is caught there.
+
+The hardware layer cannot run in CI (the runner has no GPU). It is checked by hand on the reference RTX 4070 through [hardware-checks.md](hardware-checks.md); record the results there.
+
+## Releases
+
+The version lives in `src/core/version.hpp`. Pushing a `v*` tag that matches it builds, tests and packages both executables into a **draft** GitHub release with a SHA-256 file and a build provenance attestation; publishing the draft is a manual step. Running the Release workflow by hand is a dry run that only uploads the zip as an artifact.
diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png
new file mode 100644
index 0000000..4cd2992
Binary files /dev/null and b/docs/images/dashboard.png differ
diff --git a/docs/images/icon.png b/docs/images/icon.png
new file mode 100644
index 0000000..2ceb264
Binary files /dev/null and b/docs/images/icon.png differ