# 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/llms.txt

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. 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 ready-to-build Premake workspace with the guide's layout, base header, logging and asserts, the appendix configs, a sample app, a sandbox, a first test and an MSVC CI workflow. It is generated from this guide and follows it, so prefer it over scaffolding a new codebase by hand.

Download it as a zip, renamed to the user's names, with a GET request (query parameters are optional; missing ones take their defaults, invalid ones return HTTP 400 with a message):

```bash
curl -fsSL "https://style.chopper.dev/cpp/template/download?name=Project&code=PRJ" -o Project.zip && unzip Project.zip
```

Parameters:

- `name`: Project name. PascalCase. Becomes the root namespace and the folder, library and app names. Default `Project`. Must match `^(?:[A-Z][A-Za-z0-9]{1,31})$`.
- `code`: Short code. 2 or 3 letters. Prefixes every macro and names the precompiled header. Default `PRJ`. Must match `^(?:[A-Z]{2,3})$`.

After unzipping:

1. **Run setup.** scripts/Setup.sh (or .bat) adds spdlog and doctest as submodules, formats the sources and generates project files.
2. **Build.** Makefiles on macOS and Linux, a Visual Studio solution on Windows. Debug, Release and Dist build warning-free.

## 02. Language baseline and toolchain

- **MUST** build with MSVC (latest Visual Studio, `/std:c++latest` until `/std:c++23` is stable) through the Premake-generated `.sln`.
- **MUST** build warning-free at `/W4` with `/permissive-`, `/Zc:__cplusplus` and `/Zc:preprocessor`.
- **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.
- **MUST NOT** rely on compiler extensions without a wrapper macro in the base header (see section 4).
- **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

- **MUST** keep one class per header/source pair, file named after the class: `ShaderCache.h`, `ShaderCache.cpp`.
- **MUST** put OS, windowing and graphics API includes (`<Windows.h>`, `<vulkan/vulkan.h>`, Metal, SDL) only under `Platform/`. Everything else talks to an interface.
- **MUST** include project headers by their path from `src/`: `#include "Project/Gpu/Shader.h"`, never `"../Gpu/Shader.h"`.
- **SHOULD** split a domain folder once it passes about 15 files, by sub-feature rather than by kind (`Gpu/Pipeline/`, not `Gpu/Structs/`).
- **SHOULD** keep `Sandbox` compiling. It is where new subsystems get exercised before an app depends on them.
- **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.
- **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

- **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`.
- **MUST** name the precompiled header after the code: `prjpch.h` / `prjpch.cpp`.
- **MUST** use the full project name, not the code, as the root namespace: `namespace Project`. Codes are for the preprocessor only.
- **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.
- **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

- **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.
- **SHOULD** name output parameters `out` + Name (`outPixels`) on the rare occasions they are used (see section 10).
- **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.
- **AVOID** encoding types in names (`pBuffer`, `strName`, `uiCount`). The `m_` / `s_` / `g_` scope prefix is the only prefix.
- **AVOID** names that differ only in case or a trailing digit (`Buffer` vs `buffer`, `pass2`).
- **MUST** match vendor naming at the boundary only. A wrapper around `VkImage` is `Image` with a method `GetHandle()`, not `vk_image_wrapper`.
- **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`.
- **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`.
- **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).
- **MUST NOT** abbreviate by deleting letters inside a word: `customerId`, not `cstmrId`; `buffer`, not `bfr`.
- **MUST** capitalize an abbreviation as one word: `LoadDdsFile()`, `HttpClient`, `GpuError`, not `LoadDDSFile()`. Vendor names keep their own spelling (`VkSwapchainKHR`).
- **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

- **MUST** close every namespace with a `// namespace X` comment.
- **MUST** use `// clang-format off` only around tables of data (lookup arrays, opcode maps), and switch it back on immediately after.
- **AVOID** column-aligning assignments or comments by hand; one rename breaks the alignment and the diff.
- **MUST** put the base class on the same line as the class name, within the 80-column limit.
- **MUST** indent `public:`, `protected:` and `private:` 1 space, and omit any section that would be empty.
- **SHOULD** put a blank line before each access keyword except the first. Small classes may skip it.
- **MUST NOT** leave a blank line after an access keyword.
- **MUST** put one space after `if`, `for`, `while` and `switch`, and one before `{`.
- **MUST NOT** put spaces just inside the parentheses. Inside a `for` header, put one space after each semicolon.
- **MUST** brace every controlled statement, with a line break after `{` and before `}`. An empty body may be `{}` on one line.
- **MUST** keep `} else {` and `} else if (...) {` on the closing-brace line.
- **SHOULD** declare variables needed only by the statement in its init clause: `if (auto it = m_Cache.find(key); it != m_Cache.end()) {`.
- **MUST** write an empty loop body as `{}` or `continue;`, never a lone `;`.
- **MAY** brace individual `case` blocks; when braced, the brace follows the label and the body is indented one more level.
- **MUST NOT** put a space between `&` and the name in a by-reference capture: `[&total]`.
- **SHOULD** write short lambdas inline as arguments.
- **MUST NOT** put spaces around `.` or `->`, or after a unary `*` or `&`: `x = *ptr;`, `ptr = &x;`, `v = obj->Field;`.
- **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*>`.
- **MAY** declare several variables in one statement only when none of them has `*` or `&`: `int x, y;` is fine, `int x, *y;` is not.
- **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);`.
- **MAY** parenthesize a long expression that is broken across lines, where the parentheses help the reader see where it ends.
- **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`.
- **MAY** initialize a floating-point variable from an integer literal the type can represent exactly: `float scale = 1;`.
- **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.
- **MUST** encode source files as UTF-8 (MSVC builds with `/utf-8`).
- **SHOULD** use escapes for invisible or confusable characters: `"\uFEFF"`, not a literal byte-order mark.
- **AVOID** the `u8` prefix; its meaning changed in C++20 (`char8_t`).
- **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

- **MUST** start every header with `#pragma once`. No include guards.
- **MUST** make every header self-sufficient: a `.cpp` containing only that include must compile. The `.test.cpp` next to it proves this for free.
- **MUST** keep `using namespace` out of headers entirely. In a `.cpp`, only inside a function or for `std::literals`.
- **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`).
- **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.
- **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.
- **AVOID** umbrella headers inside the library. `Project.h` exists for apps; library code includes exactly what it uses.
- **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

- **MUST NOT** write `new` or `delete` outside allocator and container implementations.
- **MUST** use `enum class` with an explicit underlying type for anything stored or serialized.
- **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.
- **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`).
- **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.
- **AVOID** `std::shared_ptr` cycles; break them with `std::weak_ptr` or an owning parent. If two `Ref`s point at each other, redesign.
- **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

- **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.
- **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}`.
- **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`.
- **MAY** use `struct` for stateless types such as traits, template metafunctions and small functors, to match the standard library.
- **SHOULD** follow the rule of zero: let `Scope`, `std::vector` and RAII wrappers handle cleanup so the compiler-generated members are right.
- **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.
- **MUST** mark single-argument constructors and conversion operators `explicit`.
- **SHOULD** mark classes `final` unless designed for inheritance. Base classes intended for inheritance get a `virtual` destructor or a `protected` non-virtual one.
- **MUST** mark every override `override`, never repeat `virtual` on it.
- **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.
- **AVOID** deep inheritance. Two levels (interface, implementation) is the norm; a third needs a reason.
- **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.
- **AVOID** `friend` except for tests and tightly paired types in the same file.

## 10. Functions

- **SHOULD** return values, structs or `std::expected` rather than out-parameters. Return value optimization makes this free.
- **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.
- **MUST** mark functions `noexcept` when they cannot throw and sit on a hot path or in a move operation.
- **SHOULD** keep to about 4 parameters. Past 4, take a `*Desc` struct with designated initializers.
- **AVOID** `bool` parameters at call sites. `Upload(data, true)` says nothing; use an `enum class` or two named functions.
- **AVOID** default arguments on virtual functions; the default comes from the static type.
- **SHOULD** prefer early returns over nested `if` blocks. Guard clauses first, happy path last, unindented.
- **MUST** initialize in the declaration rather than declaring and then assigning: `int jobs = CountJobs();`, not `int jobs; jobs = CountJobs();`.
- **SHOULD** start containers initialized: `std::vector<int> ids = {1, 2};` rather than a series of `push_back` calls.
- **SHOULD** declare variables used only by an `if`, `while` or `for` inside that statement, so they cannot leak past it.
- **SHOULD** hoist an object out of a loop when constructing and destroying it every iteration is costly, and reuse it instead.
- **SHOULD** use lambdas where they make code clearer, formatted as in section 6.
- **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.
- **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.
- **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.
- **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.
- **MUST NOT** capture references into a lambda that is queued or run on another thread. Capture by value, or capture a `std::weak_ptr`.
- **SHOULD** name a lambda (`const auto`) once it passes about 5 lines or is used twice.

## 11. Error handling

- **MUST NOT** throw exceptions in project code. Projects build with exceptions enabled only so vendor libraries that need them work.
- **MUST** catch at the boundary any exception a vendor library can throw, and convert it to an `Error` value right there.
- **MUST NOT** let any exception cross a DLL, C ABI, script VM or thread boundary.
- **SHOULD** give each subsystem one small `enum class` error (`GpuError`, `IoError`) plus, when context matters, a struct carrying the enum and a message.
- **MUST** propagate errors with early returns; an `expected` is never silently dropped (the `[[nodiscard]]` rule enforces this).
- **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

- **MUST** mark member functions `const` when they do not change observable state. Use `mutable` only for caches and mutexes, with a comment.
- **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.
- **MUST** use `constexpr` for constants, never `#define` or `static const` in headers.
- **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.
- **SHOULD** use `consteval` for functions that must never run at runtime: compile-time string hashing, opcode table generation, validated format strings.
- **SHOULD** use `if constexpr` instead of tag dispatch or SFINAE when a template branches on a type property.
- **SHOULD** `static_assert` every layout assumption: struct sizes and offsets that match hardware, file formats or another process's memory.
- **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

- **MUST** constrain template parameters with a concept or `requires` clause. Prefer the short form: `template <std::integral T>`.
- **SHOULD** name project concepts after what the type can do (`Serializable`, `GpuResource`) and keep them in the domain that uses them.
- **SHOULD** replace CRTP with deducing `this` where MSVC supports it in the pinned toolchain; keep CRTP only where it does not.
- **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.
- **AVOID** SFINAE (`std::enable_if`) in new code; concepts replace it.
- **AVOID** template metaprogramming that a `constexpr` function could do. Readable compile-time code beats clever type lists.
- **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

- **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.
- **SHOULD** `reserve()` vectors whose final size is known or bounded.
- **SHOULD** use a per-frame linear allocator for transient data (render commands, temporary strings) and reset it once per frame.
- **SHOULD** route subsystem allocations through a named allocator so the memory profiler can attribute them (`PRJ_ALLOC_TAG("Gpu")`).
- **SHOULD** store hot data as structure-of-arrays when a loop touches one or two fields of many objects (chunk voxels, particles, entity transforms).
- **SHOULD** order struct members largest to smallest to avoid padding in types stored in bulk; `static_assert` the size.
- **MUST** make move constructors and move assignment `noexcept`, or `std::vector` will copy on growth.
- **SHOULD** pass `std::move` only when the source is not used afterward, and never `return std::move(local);` (it blocks copy elision).
- **AVOID** virtual calls inside per-element loops. Batch by type, or resolve the call once outside the loop.

## 15. Concurrency

- **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.
- **MUST** name every long-lived thread (`SetThreadName("Gpu Submit")`) so it shows up in the debugger and profiler.
- **MUST** use `std::jthread` with a `std::stop_token` for long-lived threads; never detach.
- **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.
- **SHOULD** pass data between threads through queues (SPSC or MPMC ring buffers) instead of shared fields.
- **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.
- **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.
- **AVOID** `volatile` for synchronization. It means "memory-mapped hardware", nothing more.
- **AVOID** holding a lock while calling into code you do not own (callbacks, logging sinks, vendor APIs).

## 16. Platform abstraction and API boundaries

- **MUST** keep `#ifdef PRJ_PLATFORM_*` out of non-platform files. Select platform sources in Premake with `filter "system:windows"` instead.
- **SHOULD** give each platform folder the same file names (`Platform/Windows/WindowsWindow.cpp`, `Platform/MacOS/MacOSWindow.mm`) so the parallel structure is obvious.
- **SHOULD** wrap OS handles in RAII types inside `Platform/` (`UniqueHandle`, `UniqueVkObject<T>`) so leaks are impossible by construction.
- **MUST** export only `extern "C"` functions taking and returning C types: fixed-width integers, `const char*`, plain structs, opaque handles.
- **MUST** version every exported struct with a leading `uint32_t StructSize` (or a version field) so it can grow without breaking callers.
- **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.
- **MUST** let the module that allocates memory also free it. Export a matching `prj_free_*` for anything returned across the boundary.
- **MUST NOT** pass `std::string`, `std::vector`, smart pointers or exceptions across the boundary.
- **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

- **MUST** use `std::format`-style placeholders (`"{}"`), never printf specifiers or string concatenation.
- **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.
- **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)`.
- **SHOULD** log at `TRACE` inside loops and only behind a category or verbosity check.
- **SHOULD** log state changes and decisions ("Swapchain recreated at 2560x1440"), not function entry and exit.
- **MUST NOT** log secrets, tokens, credentials or personal data, in any configuration.
- **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

- **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.
- **MUST** keep `*.test.cpp` files out of the shipping library and app targets (Premake `removefiles` on the library, `files` on the test project).
- **MUST** add a test with every bug fix that reproduces the bug first.
- **SHOULD** test through the public interface. Reaching into privates means the class wants splitting.
- **SHOULD** name test cases as behavior sentences: `TEST_CASE("Decoder rejects a branch with a misaligned target")`.
- **SHOULD** make tests deterministic: fixed seeds, no wall-clock time, no network, no GPU unless the test project is explicitly a GPU test.
- **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.
- **SHOULD** run tests in CI under Debug with AddressSanitizer and UndefinedBehaviorSanitizer (Clang), and under MSVC with `/fsanitize=address`.
- **AVOID** mocks for everything. Fake the platform layer at the interface (a `HeadlessWindow`, a `NullDevice`) and test real logic against it.

## 19. Build system

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

## 20. Comments and documentation

- **MUST** write comments with `//`. Google allows `//` or `/* */` as long as a codebase is consistent; these projects pick `//` everywhere, doc comments included.
- **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`.
- **MUST** write `TODO` and `FIXME` with an owner or issue: `// TODO(owner): handle swapchain loss during resize (#42)`. An anonymous TODO is noise.
- **SHOULD** use `// NOTE:` for surprising-but-correct code and `// HACK:` for knowingly wrong code with a removal plan.
- **AVOID** commented-out code. Git remembers it.
- **AVOID** banner comments and ASCII-art separators except in `Base.h`-style headers that group many macros.
- **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.
- **SHOULD** give the reader enough to know when and how to use the type, plus anything needed to use it correctly.
- **MUST** state the threading assumptions. If instances can be used from several threads, spell out the rules and invariants for doing so.
- **SHOULD** include a short usage example when one makes the intent obvious.
- **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`.
- **MUST** start with a verb phrase, with "This function" implied: "Opens the file", not "Open the file".
- **SHOULD** describe what, not how. The how belongs in the definition.
- **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.
- **SHOULD NOT** be verbose or state the obvious. On an override, document only what is specific to the override, often nothing.
- **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

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