Repository Conventions
Directory layout
| Path |
Contents |
src/ |
C++ application source. New types live in src/<TypeName>.{h,cpp} directly under src/, or under src/<area>/ once an area grows several related files. |
tests/ |
doctest unit tests. One file per area, CamelCase: tests/<TypeName>Test.cpp. The doctest entry point is tests/main.cpp. |
scripts/ |
Thin build/test wrappers (build.{ps1,sh,cmd}, test.{ps1,sh,cmd}). |
docs/ |
Long-form documentation: ROADMAP.md, ISSUES.md. |
.github/ |
CI workflows (.github/workflows/build.yml) and other GitHub config. |
.claude/ |
Agent skills under .claude/skills/<skill-name>/SKILL.md. |
CMakeLists.txt |
Top-level build definition. The test target is wired up from tests/CMakeLists.txt. |
.editorconfig, .clang-format |
Style sources of truth (see the format skill). |
Where to put new code
- New domain code (e.g.
Paddle, Ball, Score): create src/<TypeName>.h and src/<TypeName>.cpp directly under src/. If an area grows enough to need a folder (several related files), promote it to src/<area>/ with the same CamelCase rule for individual files.
- Wire new sources into the
PONG_SRC variable in the top-level CMakeLists.txt. Use the existing ${CMAKE_CURRENT_SOURCE_DIR}/src/<TypeName>.cpp form so the variable can be consumed from tests/CMakeLists.txt unchanged. Both pong-sdl3-cpp and pong-sdl3-cpp-tests read this single list, so adding a file means editing exactly one place. Entry-point sources (src/main.cpp, tests/main.cpp) stay off the list because each binary supplies its own.
- Per-target settings (
target_link_libraries, target_compile_options, Windows DLL-copy steps) are intentionally duplicated between the two targets. Keep the two blocks visually parallel so a reviewer can spot drift in one glance; promote to a helper macro only if they grow.
- Add the matching test file as
tests/<TypeName>Test.cpp. Append it to tests/CMakeLists.txt so it is compiled into pong-sdl3-cpp-tests; doctest_discover_tests() will register every TEST_CASE automatically. Test sources can #include "<TypeName>.h" directly because both targets carry src/ on their include path.
Naming
- Source files use CamelCase matching the class, struct, or dominant namespace they contain. Example: a class
Application lives in Application.h (header) and Application.cpp (implementation). Files that contain no class or struct (entry points, free-function modules) keep their conventional lowercase name; the most common case is main.cpp.
- Header extension is
.h; implementation extension is .cpp.
- Test files mirror the unit they test, in CamelCase, with a
Test suffix: tests for Application go into tests/ApplicationTest.cpp. The doctest entry point stays at tests/main.cpp.
- CMake target names use kebab-case (
pong-sdl3-cpp, pong-sdl3-cpp-tests).
Namespaces
- Pure free-function modules wrap their API in a namespace matching the file/module name.
TextRenderer.h exposes TextRenderer::drawText, TextRenderer::drawTextCentered, etc.; PlayfieldLayout.h exposes PlayfieldLayout::ball; the constants in Playfield.h live in Playfield::. New modules of this shape (PaddleMotion::stepCenterY, PaddleMotion::clampCenterY) follow the same pattern. The namespace lets function names drop the redundant module prefix (stepCenterY rather than stepPaddleCenterY) and makes the call site self-documenting about which header to include.
- Struct- or class-dominant headers keep their associated free functions flat.
Paddle.h ships struct Paddle plus the free helpers makePaddle and toFRect; Score.h ships struct Score plus setScore; both stay outside any namespace. The reason is mechanical — a struct Paddle and a namespace Paddle cannot coexist — and conceptual: the helpers belong to the struct's API surface, so the struct name already disambiguates them at the call site. The same applies to class-dominant headers (RandomSourceMt19937).
English convention
Use American English spelling throughout the project: identifier names, parameter names, code comments, doctest case names, commit messages, PR descriptions, docs/, README.md, and skill files. The same convention applies to agent-to-user communication in chat and PR reviews so the in-repo wording and the conversation around it stay aligned.
Quick reference for the spellings most likely to drift in this codebase:
| Use |
Not |
color |
colour |
center / centered / centering / centers |
centre / centred / centring / centres |
behavior |
behaviour |
gray |
grey |
modeled / modeling |
modelled / modelling |
-ize / -ization endings (optimize, recognize, realize, analyze, emphasize, organize, categorize) |
-ise / -isation |
license (both noun and verb) |
licence (UK noun) |
defense, meter, theater, honor, favor, labor, neighbor |
defence, metre, theatre, honour, favour, labour, neighbour |
judgment |
judgement |
The convention is enforced by review, not tooling. When introducing a new identifier or rewriting a comment block, prefer the American spelling from the start rather than relying on a follow-up rename. Pre-existing identifiers that still use British spelling should be renamed in their own focused commit (covering the definition and every call site together) so reviewers can read the rename in isolation.
Code comments
- Describe architecture, design goals, and why, not how. The code shows the "how"; comments earn their keep by explaining the choice, the invariant, or the rejected alternative.
- Cut anything the code, the file name, or the signature already says. A tight intent comment beats a paragraph re-narrating the code below. "Concise" applies to content, not line length: still wrap at the same 120-column limit as code (see the
format skill) and run clang-format -i on touched files so reflow stays consistent.
- Drop anything that ages badly or restates universal hygiene. In particular: forward-looking promises about future milestones (
"will be revisited by the Paddle-controls milestone"), generic C++ admonitions that apply everywhere ("do not hard-code these outside this header"), parentheticals that re-state the surrounding context ("Static-playfield tuning constants" inside Playfield.h), and (see foo.cpp) cross-references the reader can grep for in two seconds. Keep the comment if it explains this choice or invariant; cut it if it just states a general best practice.
- Skip obvious comments.
// Returns the elapsed time in seconds above double secondsBetween(...) is noise — the signature already says it.
- Public-API contract notes (preconditions, defensive behavior, ownership) are fine in headers when they document something the signature itself cannot.
Build & test commands
Use the wrappers, never hand-rolled cmake/ctest invocations, unless the wrappers are unavailable:
- Build:
scripts\build.ps1 (Windows) or ./scripts/build.sh (Linux/macOS) — see the build skill.
- Test:
scripts\test.ps1 or ./scripts/test.sh — see the test skill.
- Format:
clang-format -i … / clang-format --dry-run --Werror … — see the format skill.
Documentation cross-references
1---2name: repo-conventions3description: Repository layout and naming conventions. Use to find or place files.4---56# Repository Conventions78## Directory layout910| Path | Contents |11| --- | --- |12| `src/` | C++ application source. New types live in `src/<TypeName>.{h,cpp}` directly under `src/`, or under `src/<area>/` once an area grows several related files. |13| `tests/` | doctest unit tests. One file per area, CamelCase: `tests/<TypeName>Test.cpp`. The doctest entry point is `tests/main.cpp`. |14| `scripts/` | Thin build/test wrappers (`build.{ps1,sh,cmd}`, `test.{ps1,sh,cmd}`). |15| `docs/` | Long-form documentation: `ROADMAP.md`, `ISSUES.md`. |16| `.github/` | CI workflows (`.github/workflows/build.yml`) and other GitHub config. |17| `.claude/` | Agent skills under `.claude/skills/<skill-name>/SKILL.md`. |18| `CMakeLists.txt` | Top-level build definition. The test target is wired up from `tests/CMakeLists.txt`. |19| `.editorconfig`, `.clang-format` | Style sources of truth (see the `format` skill). |2021## Where to put new code2223- New domain code (e.g. `Paddle`, `Ball`, `Score`): create `src/<TypeName>.h` and `src/<TypeName>.cpp` directly under `src/`. If an area grows enough to need a folder (several related files), promote it to `src/<area>/` with the same CamelCase rule for individual files.24- Wire new sources into the **`PONG_SRC` variable** in the top-level `CMakeLists.txt`. Use the existing `${CMAKE_CURRENT_SOURCE_DIR}/src/<TypeName>.cpp` form so the variable can be consumed from `tests/CMakeLists.txt` unchanged. Both `pong-sdl3-cpp` and `pong-sdl3-cpp-tests` read this single list, so adding a file means editing exactly one place. Entry-point sources (`src/main.cpp`, `tests/main.cpp`) stay off the list because each binary supplies its own.25- Per-target settings (`target_link_libraries`, `target_compile_options`, Windows DLL-copy steps) are intentionally duplicated between the two targets. Keep the two blocks visually parallel so a reviewer can spot drift in one glance; promote to a helper macro only if they grow.26- Add the matching test file as `tests/<TypeName>Test.cpp`. Append it to `tests/CMakeLists.txt` so it is compiled into `pong-sdl3-cpp-tests`; `doctest_discover_tests()` will register every `TEST_CASE` automatically. Test sources can `#include "<TypeName>.h"` directly because both targets carry `src/` on their include path.2728## Naming2930- Source files use **CamelCase** matching the class, struct, or dominant namespace they contain. Example: a class `Application` lives in `Application.h` (header) and `Application.cpp` (implementation). Files that contain no class or struct (entry points, free-function modules) keep their conventional lowercase name; the most common case is `main.cpp`.31- Header extension is `.h`; implementation extension is `.cpp`.32- Test files mirror the unit they test, in CamelCase, with a `Test` suffix: tests for `Application` go into `tests/ApplicationTest.cpp`. The doctest entry point stays at `tests/main.cpp`.33- CMake target names use kebab-case (`pong-sdl3-cpp`, `pong-sdl3-cpp-tests`).3435## Namespaces3637- **Pure free-function modules wrap their API in a namespace matching the file/module name.** `TextRenderer.h` exposes `TextRenderer::drawText`, `TextRenderer::drawTextCentered`, etc.; `PlayfieldLayout.h` exposes `PlayfieldLayout::ball`; the constants in `Playfield.h` live in `Playfield::`. New modules of this shape (`PaddleMotion::stepCenterY`, `PaddleMotion::clampCenterY`) follow the same pattern. The namespace lets function names drop the redundant module prefix (`stepCenterY` rather than `stepPaddleCenterY`) and makes the call site self-documenting about which header to include.38- **Struct- or class-dominant headers keep their associated free functions flat.** `Paddle.h` ships `struct Paddle` plus the free helpers `makePaddle` and `toFRect`; `Score.h` ships `struct Score` plus `setScore`; both stay outside any namespace. The reason is mechanical — a `struct Paddle` and a `namespace Paddle` cannot coexist — and conceptual: the helpers belong to the struct's API surface, so the struct name already disambiguates them at the call site. The same applies to class-dominant headers (`RandomSourceMt19937`).3940## English convention4142Use **American English** spelling throughout the project: identifier names, parameter names, code comments, doctest case names, commit messages, PR descriptions, `docs/`, `README.md`, and skill files. The same convention applies to agent-to-user communication in chat and PR reviews so the in-repo wording and the conversation around it stay aligned.4344Quick reference for the spellings most likely to drift in this codebase:4546| Use | Not |47| --- | --- |48| `color` | `colour` |49| `center` / `centered` / `centering` / `centers` | `centre` / `centred` / `centring` / `centres` |50| `behavior` | `behaviour` |51| `gray` | `grey` |52| `modeled` / `modeling` | `modelled` / `modelling` |53| `-ize` / `-ization` endings (`optimize`, `recognize`, `realize`, `analyze`, `emphasize`, `organize`, `categorize`) | `-ise` / `-isation` |54| `license` (both noun and verb) | `licence` (UK noun) |55| `defense`, `meter`, `theater`, `honor`, `favor`, `labor`, `neighbor` | `defence`, `metre`, `theatre`, `honour`, `favour`, `labour`, `neighbour` |56| `judgment` | `judgement` |5758The convention is enforced by review, not tooling. When introducing a new identifier or rewriting a comment block, prefer the American spelling from the start rather than relying on a follow-up rename. Pre-existing identifiers that still use British spelling should be renamed in their own focused commit (covering the definition and every call site together) so reviewers can read the rename in isolation.5960## Code comments6162- Describe **architecture, design goals, and *why***, not *how*. The code shows the "how"; comments earn their keep by explaining the choice, the invariant, or the rejected alternative.63- **Cut anything the code, the file name, or the signature already says.** A tight intent comment beats a paragraph re-narrating the code below. "Concise" applies to *content*, not line length: still wrap at the same **120-column** limit as code (see the `format` skill) and run `clang-format -i` on touched files so reflow stays consistent.64- **Drop anything that ages badly or restates universal hygiene.** In particular: forward-looking promises about future milestones (`"will be revisited by the Paddle-controls milestone"`), generic C++ admonitions that apply everywhere (`"do not hard-code these outside this header"`), parentheticals that re-state the surrounding context (`"Static-playfield tuning constants"` inside `Playfield.h`), and `(see foo.cpp)` cross-references the reader can grep for in two seconds. Keep the comment if it explains *this* choice or invariant; cut it if it just states a general best practice.65- Skip obvious comments. `// Returns the elapsed time in seconds` above `double secondsBetween(...)` is noise — the signature already says it.66- Public-API contract notes (preconditions, defensive behavior, ownership) are fine in headers when they document something the signature itself cannot.6768## Build & test commands6970Use the wrappers, never hand-rolled `cmake`/`ctest` invocations, unless the wrappers are unavailable:7172- Build: `scripts\build.ps1` (Windows) or `./scripts/build.sh` (Linux/macOS) — see the `build` skill.73- Test: `scripts\test.ps1` or `./scripts/test.sh` — see the `test` skill.74- Format: `clang-format -i …` / `clang-format --dry-run --Werror …` — see the `format` skill.7576## Documentation cross-references7778- Roadmap and milestone planning: [`docs/ROADMAP.md`](../../../docs/ROADMAP.md).79- Backlog of proposed issues not yet filed on GitHub: [`docs/ISSUES.md`](../../../docs/ISSUES.md).80- Project overview, build, test, formatting: top-level [`README.md`](../../../README.md).