# C++ Style Guide: rules digest

> Every rule from the C++ Style Guide (rev. 2026.10), without the prose, tables and examples. The full guide: https://style.chopper.dev/cpp@2026.10/llms.txt

Revision 2026.10, released 2026-10-19. This pinned URL never changes; the latest text is at https://style.chopper.dev/cpp/rules.md. Changelog: https://style.chopper.dev/cpp/changelog.md.

MUST / MUST NOT are non-negotiable. SHOULD / SHOULD NOT are the default; deviate only with a short comment saying why. MAY is allowed when it helps. AVOID is allowed but a smell. Cite a rule by its ID (CPP-02.1): IDs never change or get reused, and https://style.chopper.dev/cpp@2026.10#CPP-02.1 links to the rule. Code samples use `PRJ` as the project short code (`prj` in lowercase contexts) and `Project` as the root namespace. Both are placeholders; substitute your own project's code and name. When the guide is silent, match the surrounding code, then the Google C++ Style Guide, then the C++ Core Guidelines, in that order.

## Starter project

A starter project that follows this guide exists, but templates are not versioned: see the latest guide at https://style.chopper.dev/cpp/llms.txt for its download URL and parameters.

## 02. Language baseline and toolchain

- `CPP-02.1` **MUST** build with MSVC (latest Visual Studio, `/std:c++latest` until `/std:c++23` is stable) through the Premake-generated `.sln`.
- `CPP-02.2` **MUST** build warning-free at `/W4` with `/permissive-`, `/Zc:__cplusplus` and `/Zc:preprocessor`.
- `CPP-02.3` **SHOULD** also build with Clang (`-std=c++23 -Wall -Wextra -Wpedantic`) on macOS ARM and Linux. Treat a Clang-only warning as a real bug until proven otherwise.
- `CPP-02.4` **MUST NOT** rely on compiler extensions without a wrapper macro in the base header (see section 4).
- `CPP-02.5` **SHOULD** check a feature against the MSVC column of cppreference's compiler support table before using it. If MSVC lacks it, wait or wrap it.

## 03. Repository and project layout

- `CPP-03.1` **MUST** keep one class per header/source pair, file named after the class: `ShaderCache.h`, `ShaderCache.cpp`.
- `CPP-03.2` **MUST** put OS, windowing and graphics API includes (`<Windows.h>`, `<vulkan/vulkan.h>`, Metal, SDL) only under `Platform/`. Everything else talks to an interface.
- `CPP-03.3` **MUST** include project headers by their path from `src/`: `#include "Project/Gpu/Shader.h"`, never `"../Gpu/Shader.h"`.
- `CPP-03.4` **SHOULD** split a domain folder once it passes about 15 files, by sub-feature rather than by kind (`Gpu/Pipeline/`, not `Gpu/Structs/`).
- `CPP-03.5` **SHOULD** keep `Sandbox` compiling. It is where new subsystems get exercised before an app depends on them.
- `CPP-03.6` **AVOID** `Utils/`, `Helpers/` or `Common/` folders. A helper belongs to the domain that needs it; move it to `Core/` only when three domains need it.
- `CPP-03.7` **MUST NOT** modify files under `vendor/`. Patch through a fork, a wrapper, or a Premake define, and note it in `Dependencies.lua`.

## 04. Project short code and the base header

- `CPP-04.1` **MUST** prefix every macro with the code: `PRJ_ASSERT`, `PRJ_PLATFORM_MACOS`, `PRJ_BIT`. Unprefixed macros are allowed only as `#undef`-ed locals inside a single `.cpp`.
- `CPP-04.2` **MUST** name the precompiled header after the code: `prjpch.h` / `prjpch.cpp`.
- `CPP-04.3` **MUST** use the full project name, not the code, as the root namespace: `namespace Project`. Codes are for the preprocessor only.
- `CPP-04.4` **MUST** keep `Core/Base.h` as the single header that defines platform detection, build configuration, compiler wrappers, and the `Scope` / `Ref` aliases. Every other header can rely on it through the pch.
- `CPP-04.5` **SHOULD** prefer `constexpr` variables, `inline` functions and templates over macros. A macro earns its place only when it needs `__FILE__` / `__LINE__`, stringizing, or must vanish in a configuration.

## 05. Naming

- `CPP-05.1` **MUST** keep `m_` and `s_` off public fields of plain-data structs. The prefix means "guarded by this class's invariants"; a POD has none.
- `CPP-05.2` **SHOULD** name output parameters `out` + Name (`outPixels`) on the rare occasions they are used (see section 10).
- `CPP-05.3` **SHOULD** pick getter names without `Get` when the value is cheap and obvious only if the whole class does the same. Mixed styles inside one class are not allowed.
- `CPP-05.4` **AVOID** encoding types in names (`pBuffer`, `strName`, `uiCount`). The `m_` / `s_` / `g_` scope prefix is the only prefix.
- `CPP-05.5` **AVOID** names that differ only in case or a trailing digit (`Buffer` vs `buffer`, `pass2`).
- `CPP-05.6` **MUST** match vendor naming at the boundary only. A wrapper around `VkImage` is `Image` with a method `GetHandle()`, not `vk_image_wrapper`.
- `CPP-05.7` **SHOULD** make a name as descriptive as its scope is wide. A free function in a public header carries its domain in the name; a counter in a five-line loop can be `n`.
- `CPP-05.8` **SHOULD NOT** repeat what the surrounding context already says. A local does not restate its function's name, and a `Swapchain` member is `m_Images`, not `m_SwapchainImages`.
- `CPP-05.9` **AVOID** abbreviations a reader outside the project would not know, especially acronyms. An abbreviation is probably fine if Wikipedia lists it (`fqdn`, `rgba`, `dma`) or it is universal (`i` for a loop index, `T` for a template parameter).
- `CPP-05.10` **MUST NOT** abbreviate by deleting letters inside a word: `customerId`, not `cstmrId`; `buffer`, not `bfr`.
- `CPP-05.11` **MUST** capitalize an abbreviation as one word: `LoadDdsFile()`, `HttpClient`, `GpuError`, not `LoadDDSFile()`. Vendor names keep their own spelling (`VkSwapchainKHR`).
- `CPP-05.12` **SHOULD** treat a handful of widely used names as project vocabulary, documented once in `Core/`: `Scope`, `Ref`, `Handle`, plus the short forms `dt`, `ctx`, `cmd`, `desc`, `info` and `fn`. Adding a new vocabulary name deserves a second look.

## 06. Formatting

- `CPP-06.1` **MUST** close every namespace with a `// namespace X` comment.
- `CPP-06.2` **MUST** use `// clang-format off` only around tables of data (lookup arrays, opcode maps), and switch it back on immediately after.
- `CPP-06.3` **AVOID** column-aligning assignments or comments by hand; one rename breaks the alignment and the diff.
- `CPP-06.4` **MUST** put the base class on the same line as the class name, within the 80-column limit.
- `CPP-06.5` **MUST** indent `public:`, `protected:` and `private:` 1 space, and omit any section that would be empty.
- `CPP-06.6` **SHOULD** put a blank line before each access keyword except the first. Small classes may skip it.
- `CPP-06.7` **MUST NOT** leave a blank line after an access keyword.
- `CPP-06.8` **MUST** put one space after `if`, `for`, `while` and `switch`, and one before `{`.
- `CPP-06.9` **MUST NOT** put spaces just inside the parentheses. Inside a `for` header, put one space after each semicolon.
- `CPP-06.10` **MUST** brace every controlled statement, with a line break after `{` and before `}`. An empty body may be `{}` on one line.
- `CPP-06.11` **MUST** keep `} else {` and `} else if (...) {` on the closing-brace line.
- `CPP-06.12` **SHOULD** declare variables needed only by the statement in its init clause: `if (auto it = m_Cache.find(key); it != m_Cache.end()) {`.
- `CPP-06.13` **MUST** write an empty loop body as `{}` or `continue;`, never a lone `;`.
- `CPP-06.14` **MAY** brace individual `case` blocks; when braced, the brace follows the label and the body is indented one more level.
- `CPP-06.15` **MUST NOT** put a space between `&` and the name in a by-reference capture: `[&total]`.
- `CPP-06.16` **SHOULD** write short lambdas inline as arguments.
- `CPP-06.17` **MUST NOT** put spaces around `.` or `->`, or after a unary `*` or `&`: `x = *ptr;`, `ptr = &x;`, `v = obj->Field;`.
- `CPP-06.18` **MUST** attach `*` and `&` to the type in declarations, with one space before the name: `char* name`, `const Image& image`, `int* GetPointer();`. No space before a closing `>`: `std::vector<char*>`.
- `CPP-06.19` **MAY** declare several variables in one statement only when none of them has `*` or `&`: `int x, y;` is fine, `int x, *y;` is not.
- `CPP-06.20` **MUST NOT** wrap a `return` expression in parentheses unless you would also use them in `x = expr;`. Write `return result;`, never `return (result);` or `return(result);`.
- `CPP-06.21` **MAY** parenthesize a long expression that is broken across lines, where the parentheses help the reader see where it ends.
- `CPP-06.22` **MUST** write a radix point with digits on both sides, even with an exponent: `1.0f`, `-0.5`, `1248.0e6`. Never `1.f`, `-.5` or `1248e6`.
- `CPP-06.23` **MAY** initialize a floating-point variable from an integer literal the type can represent exactly: `float scale = 1;`.
- `CPP-06.24` **SHOULD** keep non-ASCII characters rare. User-facing text does not belong in source; parsers of foreign data formats and test strings are the usual exceptions.
- `CPP-06.25` **MUST** encode source files as UTF-8 (MSVC builds with `/utf-8`).
- `CPP-06.26` **SHOULD** use escapes for invisible or confusable characters: `"\uFEFF"`, not a literal byte-order mark.
- `CPP-06.27` **AVOID** the `u8` prefix; its meaning changed in C++20 (`char8_t`).
- `CPP-06.28` **MUST NOT** use `char16_t` or `char32_t`. Use `wchar_t` only at Windows API calls inside `Platform/Windows/`, converting to UTF-8 `std::string` right there.

## 07. Headers and includes

- `CPP-07.1` **MUST** start every header with `#pragma once`. No include guards.
- `CPP-07.2` **MUST** make every header self-sufficient: a `.cpp` containing only that include must compile. The `.test.cpp` next to it proves this for free.
- `CPP-07.3` **MUST** keep `using namespace` out of headers entirely. In a `.cpp`, only inside a function or for `std::literals`.
- `CPP-07.4` **SHOULD** forward-declare instead of including when a header only uses a pointer, reference or `Scope<T>` member of the type (the destructor then goes in the `.cpp`).
- `CPP-07.5` **SHOULD** keep platform and vendor headers out of public headers. Hide them behind PIMPL or an opaque handle (`using NativeHandle = void*;`) when an interface must expose them.
- `CPP-07.6` **SHOULD** put into the pch only what is stable and widely used: the standard library, `Base.h`, `Log.h`, and at most one heavy platform header per platform. Never put a project header that changes weekly in it.
- `CPP-07.7` **AVOID** umbrella headers inside the library. `Project.h` exists for apps; library code includes exactly what it uses.
- `CPP-07.8` **AVOID** C++20 modules for now. Revisit once Premake, MSVC and Clang agree on BMI handling; record the decision in an ADR when it changes.

## 08. Types, ownership and lifetimes

- `CPP-08.1` **MUST NOT** write `new` or `delete` outside allocator and container implementations.
- `CPP-08.2` **MUST** use `enum class` with an explicit underlying type for anything stored or serialized.
- `CPP-08.3` **SHOULD** wrap meaningful numbers in small strong types once they cross a module boundary: `struct Address { uint32_t Value; };` stops a size being passed as an address. Emulators and RE tools lean on this heavily.
- `CPP-08.4` **SHOULD** use `auto` when the type is on the same line (`auto device = CreateScope<Device>()`), for iterators, and for lambdas. Spell the type when it carries meaning a reader needs (`uint32_t`, `float`).
- `CPP-08.5` **SHOULD** use `std::string` for owned strings and `std::string_view` for read-only parameters. Convert to `std::string` before handing to any API that needs null termination.
- `CPP-08.6` **AVOID** `std::shared_ptr` cycles; break them with `std::weak_ptr` or an owning parent. If two `Ref`s point at each other, redesign.
- `CPP-08.7` **AVOID** `reinterpret_cast` and C-style casts. For byte reinterpretation use `std::bit_cast`; for game memory structures, use explicit offsets and `std::memcpy`.

## 09. Classes and interfaces

- `CPP-09.1` **MUST** use `struct` only for passive objects that carry data. Every field is public, and no rule ties one field to another, because callers can change any field directly.
- `CPP-09.2` **MAY** give a struct constructors, a destructor or helper methods, as long as none of them requires or enforces an invariant. Build structs with designated initializers: `TextureDesc{.Width = 256, .Format = Format::Rgba8}`.
- `CPP-09.3` **MUST** use `class` once a type needs an invariant or more behavior, or once a widely used struct is expected to grow. When in doubt, make it a `class`.
- `CPP-09.4` **MAY** use `struct` for stateless types such as traits, template metafunctions and small functors, to match the standard library.
- `CPP-09.5` **SHOULD** follow the rule of zero: let `Scope`, `std::vector` and RAII wrappers handle cleanup so the compiler-generated members are right.
- `CPP-09.6` **MUST** follow the rule of five when a class owns a raw handle (`VkDevice`, `HANDLE`, a file descriptor): declare destructor, move constructor and move assignment; delete copy unless copying is meaningful.
- `CPP-09.7` **MUST** mark single-argument constructors and conversion operators `explicit`.
- `CPP-09.8` **SHOULD** mark classes `final` unless designed for inheritance. Base classes intended for inheritance get a `virtual` destructor or a `protected` non-virtual one.
- `CPP-09.9` **MUST** mark every override `override`, never repeat `virtual` on it.
- `CPP-09.10` **SHOULD** choose a compile-time backend (one `Create` definition per platform, selected by Premake `filter`) over a runtime switch when only one backend can exist per build.
- `CPP-09.11` **AVOID** deep inheritance. Two levels (interface, implementation) is the norm; a third needs a reason.
- `CPP-09.12` **AVOID** singletons. When one is unavoidable (`Application`), expose it through a static `Get()` that asserts it exists, and never construct it lazily from an arbitrary thread.
- `CPP-09.13` **AVOID** `friend` except for tests and tightly paired types in the same file.

## 10. Functions

- `CPP-10.1` **SHOULD** return values, structs or `std::expected` rather than out-parameters. Return value optimization makes this free.
- `CPP-10.2` **MUST** mark functions `[[nodiscard]]` when ignoring the result is a bug: anything returning `std::expected`, a handle, an owning pointer, or a computed value with no side effects.
- `CPP-10.3` **MUST** mark functions `noexcept` when they cannot throw and sit on a hot path or in a move operation.
- `CPP-10.4` **SHOULD** keep to about 4 parameters. Past 4, take a `*Desc` struct with designated initializers.
- `CPP-10.5` **AVOID** `bool` parameters at call sites. `Upload(data, true)` says nothing; use an `enum class` or two named functions.
- `CPP-10.6` **AVOID** default arguments on virtual functions; the default comes from the static type.
- `CPP-10.7` **SHOULD** prefer early returns over nested `if` blocks. Guard clauses first, happy path last, unindented.
- `CPP-10.8` **MUST** initialize in the declaration rather than declaring and then assigning: `int jobs = CountJobs();`, not `int jobs; jobs = CountJobs();`.
- `CPP-10.9` **SHOULD** start containers initialized: `std::vector<int> ids = {1, 2};` rather than a series of `push_back` calls.
- `CPP-10.10` **SHOULD** declare variables used only by an `if`, `while` or `for` inside that statement, so they cannot leak past it.
- `CPP-10.11` **SHOULD** hoist an object out of a loop when constructing and destroying it every iteration is costly, and reuse it instead.
- `CPP-10.12` **SHOULD** use lambdas where they make code clearer, formatted as in section 6.
- `CPP-10.13` **MUST** capture explicitly when the lambda may escape the current scope (stored, queued, or run on another thread). A default capture hides that a reference, or `this`, went along.
- `CPP-10.14` **MAY** use `[&]` only when the lambda's lifetime is obviously shorter than anything it could capture, such as an algorithm predicate or an immediate visitor.
- `CPP-10.15` **MAY** use `[=]` only for a short lambda that binds a few obvious variables, and never where it would capture `this` implicitly. A lambda in a member function that uses members captures `this` explicitly.
- `CPP-10.16` **MUST** use init captures only to capture, such as moving an owner in: `[texture = std::move(texture)]`. Do not use them to invent new names or change what an existing name means; declare a variable first, or write a named function object.
- `CPP-10.17` **MUST NOT** capture references into a lambda that is queued or run on another thread. Capture by value, or capture a `std::weak_ptr`.
- `CPP-10.18` **SHOULD** name a lambda (`const auto`) once it passes about 5 lines or is used twice.

## 11. Error handling

- `CPP-11.1` **MUST NOT** throw exceptions in project code. Projects build with exceptions enabled only so vendor libraries that need them work.
- `CPP-11.2` **MUST** catch at the boundary any exception a vendor library can throw, and convert it to an `Error` value right there.
- `CPP-11.3` **MUST NOT** let any exception cross a DLL, C ABI, script VM or thread boundary.
- `CPP-11.4` **SHOULD** give each subsystem one small `enum class` error (`GpuError`, `IoError`) plus, when context matters, a struct carrying the enum and a message.
- `CPP-11.5` **MUST** propagate errors with early returns; an `expected` is never silently dropped (the `[[nodiscard]]` rule enforces this).
- `CPP-11.6` **SHOULD** log an error once, at the place that decides what to do about it, not at every level it passes through.

## 12. const and compile-time code

- `CPP-12.1` **MUST** mark member functions `const` when they do not change observable state. Use `mutable` only for caches and mutexes, with a comment.
- `CPP-12.2` **SHOULD** declare locals `const` when they are not reassigned. Parameters passed by value are not marked `const` in declarations (it is noise in the header) but may be in definitions.
- `CPP-12.3` **MUST** use `constexpr` for constants, never `#define` or `static const` in headers.
- `CPP-12.4` **SHOULD** make lookup tables, hash seeds, bit masks and format descriptors `constexpr` (or `consteval`-built) so they live in read-only data with no static initialization order risk.
- `CPP-12.5` **SHOULD** use `consteval` for functions that must never run at runtime: compile-time string hashing, opcode table generation, validated format strings.
- `CPP-12.6` **SHOULD** use `if constexpr` instead of tag dispatch or SFINAE when a template branches on a type property.
- `CPP-12.7` **SHOULD** `static_assert` every layout assumption: struct sizes and offsets that match hardware, file formats or another process's memory.
- `CPP-12.8` **AVOID** non-`constexpr` globals with dynamic initialization. When a global is unavoidable, use a function-local static or construct it explicitly at startup.

## 13. Templates and concepts

- `CPP-13.1` **MUST** constrain template parameters with a concept or `requires` clause. Prefer the short form: `template <std::integral T>`.
- `CPP-13.2` **SHOULD** name project concepts after what the type can do (`Serializable`, `GpuResource`) and keep them in the domain that uses them.
- `CPP-13.3` **SHOULD** replace CRTP with deducing `this` where MSVC supports it in the pinned toolchain; keep CRTP only where it does not.
- `CPP-13.4` **SHOULD** keep template definitions in the header that declares them. When a template is large and used with few types, move the body to a `.inl` or `.cpp` and add explicit instantiations.
- `CPP-13.5` **AVOID** SFINAE (`std::enable_if`) in new code; concepts replace it.
- `CPP-13.6` **AVOID** template metaprogramming that a `constexpr` function could do. Readable compile-time code beats clever type lists.
- `CPP-13.7` **AVOID** templates as a way to skip writing an interface. If there will only ever be one or two types, write two functions.

## 14. Memory and performance

- `CPP-14.1` **MUST NOT** allocate on the heap per frame, per tick, per packet or per emulated instruction. Hot loops use pre-sized containers, frame (linear) allocators, or pools.
- `CPP-14.2` **SHOULD** `reserve()` vectors whose final size is known or bounded.
- `CPP-14.3` **SHOULD** use a per-frame linear allocator for transient data (render commands, temporary strings) and reset it once per frame.
- `CPP-14.4` **SHOULD** route subsystem allocations through a named allocator so the memory profiler can attribute them (`PRJ_ALLOC_TAG("Gpu")`).
- `CPP-14.5` **SHOULD** store hot data as structure-of-arrays when a loop touches one or two fields of many objects (chunk voxels, particles, entity transforms).
- `CPP-14.6` **SHOULD** order struct members largest to smallest to avoid padding in types stored in bulk; `static_assert` the size.
- `CPP-14.7` **MUST** make move constructors and move assignment `noexcept`, or `std::vector` will copy on growth.
- `CPP-14.8` **SHOULD** pass `std::move` only when the source is not used afterward, and never `return std::move(local);` (it blocks copy elision).
- `CPP-14.9` **AVOID** virtual calls inside per-element loops. Batch by type, or resolve the call once outside the loop.

## 15. Concurrency

- `CPP-15.1` **SHOULD** structure work as jobs over owned data (chunk generation, mesh building, asset decode) submitted to the project's job system, rather than ad hoc `std::thread`s.
- `CPP-15.2` **MUST** name every long-lived thread (`SetThreadName("Gpu Submit")`) so it shows up in the debugger and profiler.
- `CPP-15.3` **MUST** use `std::jthread` with a `std::stop_token` for long-lived threads; never detach.
- `CPP-15.4` **MUST** guard shared data with a mutex declared next to it, and keep the lock scope as small as the critical section. Use `std::scoped_lock` for more than one mutex.
- `CPP-15.5` **SHOULD** pass data between threads through queues (SPSC or MPMC ring buffers) instead of shared fields.
- `CPP-15.6` **MUST** use `std::atomic` for flags and counters touched by more than one thread. Default to `memory_order_seq_cst`; relax the order only with a comment explaining why it is safe.
- `CPP-15.7` **MUST** document thread affinity on any class that is not thread-safe: a one-line comment `// Main thread only.` above the class, backed by `PRJ_ASSERT_MAIN_THREAD()` in debug builds.
- `CPP-15.8` **AVOID** `volatile` for synchronization. It means "memory-mapped hardware", nothing more.
- `CPP-15.9` **AVOID** holding a lock while calling into code you do not own (callbacks, logging sinks, vendor APIs).

## 16. Platform abstraction and API boundaries

- `CPP-16.1` **MUST** keep `#ifdef PRJ_PLATFORM_*` out of non-platform files. Select platform sources in Premake with `filter "system:windows"` instead.
- `CPP-16.2` **SHOULD** give each platform folder the same file names (`Platform/Windows/WindowsWindow.cpp`, `Platform/MacOS/MacOSWindow.mm`) so the parallel structure is obvious.
- `CPP-16.3` **SHOULD** wrap OS handles in RAII types inside `Platform/` (`UniqueHandle`, `UniqueVkObject<T>`) so leaks are impossible by construction.
- `CPP-16.4` **MUST** export only `extern "C"` functions taking and returning C types: fixed-width integers, `const char*`, plain structs, opaque handles.
- `CPP-16.5` **MUST** version every exported struct with a leading `uint32_t StructSize` (or a version field) so it can grow without breaking callers.
- `CPP-16.6` **MUST** use a visibility macro for exports: `PRJ_API` expands to `__declspec(dllexport)` / `__declspec(dllimport)` on Windows and `__attribute__((visibility("default")))` elsewhere; build with hidden visibility by default on Clang.
- `CPP-16.7` **MUST** let the module that allocates memory also free it. Export a matching `prj_free_*` for anything returned across the boundary.
- `CPP-16.8` **MUST NOT** pass `std::string`, `std::vector`, smart pointers or exceptions across the boundary.
- `CPP-16.9` **SHOULD** name C ABI functions in `snake_case` with the lowercase code prefix (`prj_session_open`), so the boundary is visible at every call site.

## 17. Logging, assertions and diagnostics

- `CPP-17.1` **MUST** use `std::format`-style placeholders (`"{}"`), never printf specifiers or string concatenation.
- `CPP-17.2` **MUST** put `CORE` macros only in the library and plain macros only in applications. The log prefix then says which side of the line a message came from.
- `CPP-17.3` **MUST** give every assert a message saying what was expected, in words: `PRJ_CORE_ASSERT(index < m_Count, "Buffer index {} out of range ({})", index, m_Count)`.
- `CPP-17.4` **SHOULD** log at `TRACE` inside loops and only behind a category or verbosity check.
- `CPP-17.5` **SHOULD** log state changes and decisions ("Swapchain recreated at 2560x1440"), not function entry and exit.
- `CPP-17.6` **MUST NOT** log secrets, tokens, credentials or personal data, in any configuration.
- `CPP-17.7` **SHOULD** install a crash handler in Release and Dist that writes a minidump (Windows) or symbolized backtrace (macOS) plus the last 200 log lines to the app's log folder.

## 18. Testing

- `CPP-18.1` **MUST** put every test in the same folder as the code it tests, named `<name>.test.cpp` after the file under test: `Decoder.cpp` is tested by `Decoder.test.cpp` right beside it. There is no separate test tree. Premake gathers every `*.test.cpp` into one test executable per library and excludes them from the library itself.
- `CPP-18.2` **MUST** keep `*.test.cpp` files out of the shipping library and app targets (Premake `removefiles` on the library, `files` on the test project).
- `CPP-18.3` **MUST** add a test with every bug fix that reproduces the bug first.
- `CPP-18.4` **SHOULD** test through the public interface. Reaching into privates means the class wants splitting.
- `CPP-18.5` **SHOULD** name test cases as behavior sentences: `TEST_CASE("Decoder rejects a branch with a misaligned target")`.
- `CPP-18.6` **SHOULD** make tests deterministic: fixed seeds, no wall-clock time, no network, no GPU unless the test project is explicitly a GPU test.
- `CPP-18.7` **SHOULD** use golden files for emulators, decoders and renderers (expected trace output, expected image hashes), kept in a `testdata/` folder beside the `.test.cpp` that uses them, and regenerate them only in a dedicated commit.
- `CPP-18.8` **SHOULD** run tests in CI under Debug with AddressSanitizer and UndefinedBehaviorSanitizer (Clang), and under MSVC with `/fsanitize=address`.
- `CPP-18.9` **AVOID** mocks for everything. Fake the platform layer at the interface (a `HeadlessWindow`, a `NullDevice`) and test real logic against it.

## 19. Build system

- `CPP-19.1` **MUST** set `cppdialect "C++23"` (or `"C++latest"` on MSVC until 23 is accepted), `staticruntime "off"`, and `flags { "MultiProcessorCompile" }` in the workspace.
- `CPP-19.2` **MUST** treat warnings as errors (`fatalwarnings`) in CI on both MSVC and Clang; locally it may be off while iterating.
- `CPP-19.3` **MUST** keep output paths uniform: `bin/%{cfg.buildcfg}-%{cfg.system}-%{cfg.architecture}/%{prj.name}` and `bin-int/` for intermediates. Both are git-ignored.
- `CPP-19.4` **MUST** list every vendor library's include path and link name once, in `Dependencies.lua`, and reference them by table key.
- `CPP-19.5` **MUST** produce a clean MSVC build in CI on every push, even for projects developed mainly on the Mac.
- `CPP-19.6` **SHOULD** keep `GenerateProjects.bat` and `GenerateProjects.sh` in `scripts/` so a fresh clone is one command from a building solution.
- `CPP-19.7` **SHOULD** enable `/Zc:inline`, `/Zc:throwingNew` and `/utf-8` on MSVC; `-fvisibility=hidden` and `-fno-omit-frame-pointer` on Clang.
- `CPP-19.8` **AVOID** per-file compiler flags. If one file needs different flags, it probably belongs in a separate vendor project.

## 20. Comments and documentation

- `CPP-20.1` **MUST** write comments with `//`. Google allows `//` or `/* */` as long as a codebase is consistent; these projects pick `//` everywhere, doc comments included.
- `CPP-20.2` **MUST** cite the source for magic numbers and reverse-engineered facts: `// Timeout of 250 ms, per section 4.2 of the device datasheet` or `// Offset 0x1C8 confirmed against library v1.2.3`.
- `CPP-20.3` **MUST** write `TODO` and `FIXME` with an owner or issue: `// TODO(owner): handle swapchain loss during resize (#42)`. An anonymous TODO is noise.
- `CPP-20.4` **SHOULD** use `// NOTE:` for surprising-but-correct code and `// HACK:` for knowingly wrong code with a removal plan.
- `CPP-20.5` **AVOID** commented-out code. Git remembers it.
- `CPP-20.6` **AVOID** banner comments and ASCII-art separators except in `Base.h`-style headers that group many macros.
- `CPP-20.7` **SHOULD** record big decisions (a library choice, an allocation strategy, a threading model) as short ADRs in `docs/adr/NNNN-title.md`: context, decision, consequences.
- `CPP-20.8` **SHOULD** give the reader enough to know when and how to use the type, plus anything needed to use it correctly.
- `CPP-20.9` **MUST** state the threading assumptions. If instances can be used from several threads, spell out the rules and invariants for doing so.
- `CPP-20.10` **SHOULD** include a short usage example when one makes the intent obvious.
- `CPP-20.11` **SHOULD** keep comments about using the class with its declaration in the `.h`, and comments about how it works with the method definitions in the `.cpp`.
- `CPP-20.12` **MUST** start with a verb phrase, with "This function" implied: "Opens the file", not "Open the file".
- `CPP-20.13` **SHOULD** describe what, not how. The how belongs in the definition.
- `CPP-20.14` **SHOULD** cover, where it applies: the inputs and outputs, with parameter names in backticks; whether the object keeps a pointer or reference argument after the call returns; whether each pointer argument may be null and what happens if it is; what happens to existing state in an output argument (appended to or overwritten); and any performance cost a caller should know about.
- `CPP-20.15` **SHOULD NOT** be verbose or state the obvious. On an override, document only what is specific to the override, often nothing.
- `CPP-20.16` **SHOULD** say what a constructor does with its arguments (for example, taking ownership) and what cleanup a destructor does. Skip the comment when that is trivial; most destructors need none.

## 21. Git and review

- `CPP-21.1` **MUST** use the subsystem folder name as the scope (`gpu`, `cpu`, `core`, `platform`).
- `CPP-21.2` **MUST** keep formatting-only and rename-only changes in their own commits so real diffs stay readable.
- `CPP-21.3` **SHOULD** mark breaking API changes with `!` (`feat(api)!: rename prj_open to prj_session_open`) and a `BREAKING CHANGE:` footer.
