+
+## Running on ARM64
+
+All three scripts detect the host architecture and adapt. Nothing needs a flag —
+run `setup-windows.bat` exactly as on x64. Both PowerShell halves use
+`RuntimeInformation.OSArchitecture` rather than `%PROCESSOR_ARCHITECTURE%`,
+because an emulated PowerShell reports the *emulated* architecture in the
+environment variable and the real one through the API.
+
+**The build toolchain is fully native.** Visual Studio's ARM64 installer, MSVC
+and clang-cl are all ARM64 binaries, and MSVC cross-compiles every target from
+an ARM64 host with no emulation in the compiler:
+
+```bat
+cmake -B build -A ARM64 && cmake --build build --config Release :: native
+cmake -B build -A x64 && cmake --build build --config Release :: cross
+cmake -B build -A Win32 && cmake --build build --config Release :: cross
+```
+
+**ARM64 uses Visual Studio 2026** (`$VsChannel = 18`) while x64 stays on VS 2022.
+Nothing forces that split — the Arm box simply has no reason to stay on the older
+generation, since the `v141`/XP toolset that pins x64 there cannot run on Arm at
+all — so Arm takes the newer MSVC. Note this is a *default*, not a limitation:
+VS 2026 does still offer `v141` and `WinXP` as components on x64.
+
+The base component group now names `VC.Tools.x86.x64` **and** `VC.Tools.ARM64`
+explicitly instead of relying on `--includeRecommended`, which resolves
+differently per host: on an ARM64 machine the workload's recommended set is the
+ARM64-targeting toolchain, and the x64 cross-compiler is *not* implied. Spectre
+runtimes and ATL are likewise requested for both target families
+(`VC.ATL.ARM64.Spectre` is a separate component from `VC.ATL.Spectre`).
+
+**Three native compilers, on purpose.** MSVC and `clang-cl` from Visual Studio,
+plus **upstream LLVM** (`winget install LLVM.LLVM`, which resolves to
+`LLVM-<ver>-woa64.exe` on ARM64 — "Windows on Arm 64"). Upstream runs on its own
+schedule and is usually several major versions ahead of the Clang that VS
+bundles, and it installs to `C:\Program Files\LLVM` rather than inside the VS
+tree, so the two are genuinely independent. Two Clang majors plus MSVC over the
+same sources is what catches the bugs a single toolchain agrees with itself
+about. Watch the `PATH` if you use it: a bare `clang-cl` may resolve to upstream
+while CMake's `-T ClangCL` keeps using VS's, so pass a full
+`-DCMAKE_CXX_COMPILER` when it matters.
+
+**Clang/LLVM is installed on ARM64 too, and it is native there** — MSVC and
+`clang-cl` over the same sources is the compiler diversity this box is after.
+Two things establish that it is genuinely native rather than an emulated x64
+compiler: the `VC.Llvm.Clang` VSIX is `productArch=neutral` with no `chip` or
+`machineArch` restriction, so the Arm installer offers it; and MSVC's
+`VC\Tools\Llvm` tree is partitioned by **host** architecture, with real ARM64
+binaries already in `ARM64\bin`, which is where `clang-cl.exe` lands:
+
+| directory | host |
+| --- | --- |
+| `VC\Tools\Llvm\bin` | x86 |
+| `VC\Tools\Llvm\x64\bin` | x64 |
+| `VC\Tools\Llvm\ARM64\bin` | ARM64 |
+
+Unlike the `v141`/XP group, nothing technical is in the way. Upstream LLVM ships
+a Windows-on-Arm build independently too — `winget install LLVM.LLVM` resolves to
+`LLVM-<ver>-woa64.exe` on ARM64 — if you want a Clang outside Visual Studio.
+
+> Watch for a false positive when checking by hand: `VC\Tools\Llvm\*\bin` holds
+> `clang-format.exe` and `clang-tidy.exe` on **any** host, installed or not —
+> those ship with the NativeDesktop workload. Their presence does not mean the
+> compiler is there; look for `clang-cl.exe`. The verification step does exactly
+> that, and warns separately if only a non-native `clang-cl` landed on Arm.
+
+What changes, and why:
+
+| | On ARM64 |
+| --- | --- |
+| **Native ARM64** | Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal, WinMerge, Brave, Sysinternals, Claude Code, CMake, Ninja, the VS installer, **MSVC, VS clang-cl and upstream LLVM**, MSBuild, the SDK tools (`rc`, `signtool`), the in-box OpenSSH client, and the Windows Performance Toolkit. |
+| **Emulated x64** | `iperf3`, NASM, OpenCppCoverage, BinSkim, `rsync.exe`, Android GPU Inspector, and the WDK/ADK *installers* (the kits they lay down are native). |
+| **Emulated, minor** | `py.exe` (python.org ships the launcher shim as x86; it execs the native `python.exe`) and `vswhere.exe` (Microsoft ships x86 only; runs once). |
+| **Unavailable** | VirtualBox, the Windows 7 x86 test VM, Intel VTune, and the `v141` / Windows XP targeting toolset. |
+
+### The architecture audit
+
+`setup-windows-no-uac.ps1` ends with an **`ArchAudit`** step that reads the PE
+COFF header of every tool the box provisions and reports what you would actually
+run, resolved PATH-first. It reads the file itself rather than trusting package
+metadata — a multi-architecture package (Sysinternals) ships every build in one
+zip under different names, and an installer's metadata says nothing about which
+binary landed. Purely informational; it never fails the run. Skip it with
+`-Skip ArchAudit`.
+
+Anything emulated with a listed reason prints as expected. Anything emulated
+*without* one is called out, with a remedy where a native build exists — which is
+how the `ninja` problem below was found.
+
+### Two gotchas the audit surfaces
+
+- **Visual Studio bundles an x64 `ninja.exe` even on ARM64**, and `VsDevCmd.bat`
+ **prepends** the VS directories to `PATH`. So inside a Developer Command Prompt
+ — exactly where C++ builds happen — the emulated ninja wins over the native one
+ `winget install Ninja-build.Ninja` provides. It matters more than a one-off
+ tool would, because ninja is re-invoked for every edge in the build graph. Pass
+ `-DCMAKE_MAKE_PROGRAM=` the native one, or put its directory ahead of the VS
+ one. VS's bundled `cmake.exe` *is* ARM64, so only ninja has this problem.
+- **Sysinternals ships every architecture in one zip**, and the ARM64 build is
+ not the default-named exe. `ProcessExplorer.zip` contains `procexp.exe` (x86),
+ `procexp64.exe` (x64) and `procexp64a.exe` (**ARM64**) — so run the `*64a.exe`
+ variants; plain `procexp.exe` runs emulated. (`Microsoft.Sysinternals.Suite`
+ has a dedicated `SysinternalsSuite-ARM64.zip` if you prefer the whole set.)
+
+- **x64 emulation is checked, not assumed.** Everything in the "emulated" row
+ needs Prism, which is present on Windows 11 on Arm but absent on Windows 10 on
+ Arm (x86-only there) and on some Server images. The elevated half tests for
+ `System32\xtajit64.dll` up front and warns, rather than letting the failure
+ surface much later as a binary that will not start.
+- **VirtualBox is skipped.** There is no ARM64 Windows host build, and the x64
+ one cannot be rescued by emulation: a hypervisor is kernel-mode, and its x64
+ driver will not load on an ARM64 kernel. This is also what makes
+ `setup-windows-7-test-env.bat` unreachable from an ARM64 host — it prepares a
+ Windows 7 *x86* guest, which neither VirtualBox nor Hyper-V on ARM64 can run.
+ (That script itself is unchanged; it runs inside the guest, so it never sees
+ the host architecture.)
+- **The `v141` / Windows XP toolset is skipped**, and the verification step says
+ `n/a` instead of warning about it. The components are still *listed* in the
+ catalog on Arm, so this is a deliberate skip rather than a hard unavailability
+ — but the 14.16 toolset predates Windows on Arm as a host and ships
+ `HostX86`/`HostX64` compilers only, so the best you could get is an
+ x86-emulated compiler, and XP targeting is not supported from an Arm host.
+ Nothing is lost that this box could have used:
+ Windows XP never ran on ARM64, so the only point of an XP-targeting build here
+ would be producing x86 binaries, which the current toolset does natively via
+ `-A Win32`.
+- **OpenCppCoverage is installed but limited.** Unlike the rest of the emulated
+ row, this is a real ceiling rather than a slowdown: it collects coverage by
+ debugging the process under test and stepping x86/x64 instructions, so it
+ covers the x86/x64 binaries this box cross-compiles but **not** an ARM64 one.
+ Run coverage against the x64 build. The script prints this rather than leaving
+ you to discover it.
+- **BinSkim is emulated and that is fine.** The NuGet package publishes `win-x64`
+ only (its other RIDs are `linux-x64`, `linux-arm64`, `osx-x64`). BinSkim *reads*
+ PE headers and load configs, so its own architecture is independent of the
+ binaries it analyses — an emulated x64 BinSkim checks ARM64 binaries perfectly
+ well. The RID list is ordered preference, so a future `win-arm64` build is
+ picked up with no further change.
+- **`rsync` gets the x64 zip, and its `ssh.exe` is verified by running it.** The
+ release publishes x64 and x86 assets and no ARM64 one. x64 is still the right
+ pick — a transfer is bounded by the socket, not by emulated CPU, so lifting the
+ in-box client's 3 KB stdin cap wins by far more than emulation costs. But that
+ `ssh.exe` links against a `System32\libcrypto.dll` which on ARM64 is an **ARM64**
+ binary, so it may not load at all. That matters more than it sounds:
+ `rsync.exe` *prefers* an `ssh.exe` in its own directory, so a present-but-broken
+ one does not degrade to the in-box client — it breaks rsync outright, with an
+ error that names a dead remote shell rather than `ssh.exe`. The elevated half
+ therefore runs `ssh -V` after unpacking and **deletes** the binary if it will
+ not start, so `rsync` falls back to the native in-box client. The
+ `core.sshCommand` step in the non-elevated half already picked its candidate by
+ running it, and is unchanged.
+- **Intel VTune reports `n/a`** rather than printing a download link: there is no
+ Windows-on-Arm build, and its value is reading Intel PMU counters. Use the
+ Windows Performance Toolkit (`wpr`/`xperf` to collect, `wpa` to analyse), or
+ Arm Performance Studio / Streamline for Arm PMU sampling.
+- **NASM is still installed** (as emulated x64) because this box cross-compiles
+ x86/x64, where NASM is the assembler that builds them. If you only target
+ ARM64, nothing uses it — the ARM64 assembler is `armasm64.exe`, which ships
+ with MSVC.
+- **`vswhere` stays under the 32-bit Program Files** on ARM64 too
+ (`%ProgramFiles(x86)%\Microsoft Visual Studio\Installer`), so that path is
+ unchanged. The VS Installer is x86-registered there by contract even though the
+ installer binaries themselves are native.
+- **The WDK registry probe reads both hives.** `wdksetup.exe` is 32-bit and
+ writes under `WOW6432Node` on x64, but which hive a kit lands in has varied
+ across versions and architectures, and reading only one made an installed WDK
+ look absent — costing a needless multi-GB reinstall on every run.