This project follows an LLVM-inspired C++ style, tuned for emulator and systems programming.
Consistency and clarity are prioritized over cleverness.
<type>(<scope>): <description>
type= feat/fix/chore/bug/testscope= subsystem like "cpu", "core", ...
try to match commit header with branch name
example:
- in branch :
cpu/add-instr - commit :
fix(cpu): ...
Type examples:
feat → new emulator behavior
fix → bug fix
refactor → code restructure
perf → performance improvement
test → tests
ci → CI/CD
build → build system
docs → documentation
tooling → dev tools (hooks, scripts)
config → config files
chore → fallback (rare)
must use Github Issue # --> reference in header with at the end : (#<number>)
its okay to have multiple commits per issue number.
- commits are atomic (one implementation per commit; not feature)
add optional body for nontrivial commits. If you can't understand the why? or the how? (or maybe even the what lol) with the commit header ... make a body
fix(timer): reload tima on overflow edge (#17)
The previous implementation reloaded too early, which broke
timing-sensitive ROM tests. Match hardware behavior by delaying
the reload until the next machine cycle.
feat(cpu): implement adc instruction (#22) <-- no body is ok
test(cpu): add opcode regression tests (#22)
Formatting is enforced via clang-format.
- Base style: LLVM
- Indentation: 2 spaces
- Tabs: never
- Brace style: K&R (opening brace on same line)
- Column limit: ~110 characters
- Access specifiers (
public,private) are indented for readability, especially when multiple classes exist in one file.
Format a single file:
clang-format -i path/to/file.cppFormat multiple files:
clang-format -i src/**/*.cpp include/**/*.hFormat only changed lines:
git clang-format- PascalCase
namespace GameBoy
namespace Cartridge
namespace CPU- PascalCase
class MBC1;
struct RomHeader;
enum class CartridgeType;- snake_case
validate_rom_file(...)
clamp_rom_bank_(...)- snake_case
rom_size_code
ram_enabled- snake_case with trailing underscore
rom_bank_low5_
ram_enabled_- ALL_CAPS with underscores
- Prefer
constexprover macros
constexpr uint16_t ROM_BEGIN = 0x0100;
constexpr uint16_t HEADER_CHECKSUM_OFFSET = 0x014D;- Always use
enum class - Specify underlying type when relevant
enum class MBCType : uint8_t {
ROM_ONLY = 0x00,
MBC1 = 0x01,
};- Use
explicitfor all domain objects
explicit MBC1(const std::vector<uint8_t>& rom,
std::vector<uint8_t>& ram);- Use
overridefor every overridden virtual function - Use
finalfor leaf classes - Base destructors must be virtual
- Hardware-like identity objects (CPU, MBC, MMU, PPU) must not be copied or moved
MBC(const MBC&) = delete;
MBC& operator=(const MBC&) = delete;
MBC(MBC&&) = delete;
MBC& operator=(MBC&&) = delete;- Use
std::unique_ptrfor ownership - Avoid raw
new/delete - Use references or raw pointers only for non-owning access
- Code explains what
- Comments explain why
Hardware-specific logic must be commented and should reference authoritative sources (e.g., Pan Docs).
// Pan Docs §Cartridge Header:
// Bytes 0x0134–0x014C are used for header checksum computation.Always document address ranges explicitly.
// ROM bank select register (0x2000–0x3FFF).Use short block comments for non-trivial logic.
// Header checksum algorithm:
// 1. Iterate bytes 0x0134–0x014C
// 2. Subtract each byte and 1 from accumulator
// 3. Result must equal byte at 0x014DEach .cpp file should start with a short header:
// rom-validation.cpp
// Cartridge ROM header validation.
// Created by William Kiem Lafond on 2025-09-17.Keep file headers factual and brief.
- Commenting obvious code
- Large prose blocks
- Stale comments (e.g., "last modified by")
- Headers must be self-contained
- Do not use
using namespacein headers - Prefer forward declarations where reasonable
- Make illegal states unrepresentable
- Prefer early returns
- Prefer table-driven logic for hot paths
- Optimize for readability before micro-optimizations