Pet code rules

Document versionDateSummary
v12026-07-17Initial mandatory code rules
v22026-07-19Add the CMake and Python tooling boundary

Status: Mandatory development baseline

Read only the sections relevant to the current task. Every contributor must follow the whole document.

1. Precedence

Product semantics come from the specification. Architecture boundaries come from the general architecture and the active version architecture. Release scope comes from milestones, epics, and the assigned PBI.

This file defines implementation and prose rules. A lower level task cannot silently override a higher level contract.

When a task deliberately changes a higher level contract, update that contract before or with the implementation.

2. Absolute rules

  1. use British English in code, comments, documentation, errors, logs, examples, and commit messages
  2. use ISO C99 for Pet Core code
  3. keep product behaviour inside the Pet Core
  4. keep platform effects outside the Pet Core
  5. do not introduce hidden global mutable state
  6. do not claim a check passed unless it was run
  7. add or update tests with every meaningful behaviour change
  8. prefer the smallest coherent change that satisfies the assigned PBI
  9. do not add speculative abstractions or future features
  10. preserve explicit ownership, lifecycle, and failure behaviour

3. File headers

Every new code or configuration file that supports comments starts with a repository relative file header.

/// @file src/world.c

Use the native comment form when /// is invalid. Markdown, generated files, vendored files, binary files, strict JSON, lock files, and build artefacts are exempt.

4. Language and prose

4.1 British English

Use project spellings such as:

  • behaviour
  • colour
  • initialise
  • serialise
  • normalise
  • artefact

Keep external API and ecosystem names unchanged.

4.2 Plain prose

Write short and direct sentences. Avoid stock AI phrasing and ornamental repetition.

Do not use an em dash, en dash, or bare dash as sentence punctuation. Do not use a semicolon to join prose sentences. Rewrite the sentence or use two sentences. Hyphens inside compound words are allowed.

Do not use emoji or decorative Unicode characters. Project source and documentation use ASCII characters unless an external contract or real user content requires otherwise.

4.3 Comments and docstrings

Do not write comments that repeat the code.

Comments are justified only when they explain:

  • a subtle invariant
  • an ownership or lifecycle rule
  • a safety constraint
  • a non-trivial algorithm
  • platform specific behaviour
  • a deliberate trade-off

Prefer clearer code, names, and structure before adding a comment.

Comment and docstring prose starts with lower case unless a proper noun, acronym, or code identifier requires a capital. The final sentence in a comment or docstring does not end with punctuation.

Bad:

// Increment the counter.
sequence_number++;

Good:

// keep this monotonic because event ordering depends on it
sequence_number++;

Public header documentation may be longer when it defines contracts, parameters, ownership, lifetime, errors, or valid call order. It must remain concise and follow the same prose rules.

Keep a docstring to what the reader cannot get from the signature. A @note earns its place by recording an invariant, an ownership rule, a failure guarantee, or a deliberate trade-off. Restating the parameter list, narrating the implementation, or explaining why the design is good is padding. Prefer one sentence to three, and prefer no note to a note that repeats the @return line.

Four notes on one function is a signal that the function is doing too much or that the notes are filler. Two is usually plenty.

Bad:

/**
 * @brief bound one elapsed delta so the Core can accept it
 *
 * @note a reading that did not advance gives zero, which the Core accepts and
 *       treats as no time passing
 * @note a reading that went backwards gives zero rather than a huge unsigned
 *       wrap, so a clock correction cannot age the pet
 * @note a gap longer than the maximum is clamped rather than rejected, so a
 *       long suspend resumes instead of failing
 */

Good:

/**
 * @brief bound one elapsed delta so the Core can accept it
 *
 * @note a backwards or stalled reading gives zero. a gap over the maximum is
 *       clamped, so a long suspend resumes instead of failing and the skipped
 *       time is dropped
 */

In implementation files, write a short comment with // and reserve /* */ for one that runs to several lines. A single-line /* */ is noise.

Comments do not cite epic or PBI identifiers. Write the technical reason instead.

5. C99 and portability

5.1 Core baseline

  • compile Pet Core as ISO C99
  • do not require compiler extensions for correct behaviour
  • do not use operating system, graphics, filesystem, network, thread, or hardware SDK APIs in the Core
  • keep public Core headers self contained
  • use standard integer types when exact width is part of a contract
  • use size_t for object and collection sizes
  • define overflow, range, and invalid value behaviour
  • keep persisted or transferred representations independent of native padding and endianness

Portable by design is not a platform support claim. Record build, cross compilation, and runtime evidence in the active version architecture.

5.2 Ownership and allocation

  • every allocation and resource has one documented owner
  • lifecycle operations define valid call order
  • cleanup remains safe after documented partial failure states
  • getters and snapshot functions do not allocate without an explicit contract
  • no mutable internal pointer escapes through a public read operation
  • collection growth and update work remain bounded
  • heap allocation remains explicit and replaceable when a supported target requires it

5.3 Error handling

Expected failures use explicit project status values.

Validate public input before observable state mutation. A rejected operation leaves existing valid state unchanged unless its contract explicitly states otherwise.

Assertions support internal diagnostics. They do not replace required public validation.

6. Functional design

Use a functional core and imperative shell.

Prefer:

  • explicit inputs and outputs
  • deterministic domain functions
  • immutable configuration
  • local and visible mutation
  • small functions with narrow responsibilities
  • separation between decisions and effects
  • derived data instead of duplicated mutable data
  • explicit time and randomness
  • exhaustive handling of domain variants

Do not copy large state only to imitate immutability. Controlled in place mutation is acceptable when ownership is clear and tests can observe the contract.

The Pet Core does not read clocks, files, sensors, process state, or environment variables. Hosts and adapters perform those effects and supply semantic input.

7. Architecture boundaries

7.1 Pet Core

The Core owns world state, pet behaviour, semantic actions, semantic events, autonomous transitions, identity development, and presentation state derivation.

7.2 Host

The host owns application lifecycle, platform time, component composition, effect execution, and delivery of validated Core input. It does not duplicate product rules.

7.3 Frontend

A frontend owns presentation and platform input mapping. It consumes read only presentation state and produces semantic actions.

7.4 Adapter

An adapter owns source specific input, calibration, filtering, validation, grouping, and semantic translation. It does not mutate Core state directly.

7.5 Storage

Storage owns platform persistence access. The Core owns persistence semantics and validation.

8. C naming

  • public functions use pet_ followed by lower case snake case
  • public types use Pet followed by PascalCase
  • enum constants and public macros use upper case PET_ prefixes
  • source and header file names use lower case snake case
  • internal names remain specific and avoid unnecessary abbreviations
  • boolean names describe a true condition
  • names express domain meaning instead of storage or UI accidents

Do not rename third party identifiers or standard API terms.

9. API design

  • public APIs expose the minimum stable contract
  • public headers do not expose frontend, adapter, storage, test, or operating system types
  • every pointer parameter has defined null behaviour
  • every output parameter has defined success and failure behaviour
  • ownership and lifetime are documented at the boundary
  • enum and status values have explicit meanings
  • invalid lifecycle transitions fail clearly
  • snapshots and getters do not expose mutable authoritative state
  • implementation details stay private unless callers need them for ownership or allocation

10. Testing and quality

Tests accompany the behaviour they verify. Do not postpone ordinary unit coverage to a later hardening epic.

Tests should cover:

  • valid behaviour
  • invalid arguments
  • boundary values
  • rejected operations leaving state unchanged
  • lifecycle order
  • deterministic replay
  • ownership and cleanup where observable

Do not use real sleeping, wall clock timing, network access, graphics, or physical hardware in deterministic Core tests.

Report exact commands that were run. Report every required check that could not be run.

The active version architecture defines required compilers, diagnostics, and support evidence.

10.1 Tooling boundary

CMake owns the build graph. Write CMake for libraries and executables, target include directories, compile options and definitions, link libraries, generated artefacts a target needs, CTest registration, install rules, and passing a target artefact or compiler path to a tool.

Python owns verification and orchestration outside the build graph. Write Python for gate sequencing, tool availability checks, source and header discovery, parsing tool output, allowlist validation, report formatting, invoking analysis tools, and collecting release evidence.

Gate sequencing lives in the root gate.py. Check logic lives in tools/verify, one script per check, each runnable alone with python3 tools/verify/<script>.py. Building and running tests goes through CMake and CTest.

Python does not become a second build system. It does not duplicate a target dependency graph and it does not compile or link a production target. CMake does not hold parsing, reporting, or check policy that Python can hold more clearly.

Expose a check as a CMake target only when a named build target is deliberately required. A check reachable only through the build system is harder to run alone.

11. Change discipline

  • avoid unrelated formatting and renaming
  • do not combine a feature with unrelated refactoring
  • preserve user changes already present in the worktree
  • update architecture only when boundaries or durable decisions change
  • record accepted architecture choices in the version architecture or an ADR
  • record deferred limitations and bugs in their tracking documents
  • use standard relative Markdown links for project documents
  • add a revision history row when an accepted document is meaningfully revised
  • keep everything under docs/ out of Git staging and commits

12. Commit messages

All commit messages follow Conventional Commits 1.0.0.

Use this structure:

<type>[optional scope][optional !]: <description>

The description uses lower case imperative language and has no trailing full stop.

Allowed types:

feat fix chore refactor docs test build perf

Use feat for new functionality and fix for a defect correction. Use an optional short scope only when it helps identify a stable project area such as core, headless, or build.

Mark a breaking change with ! before the colon or with a BREAKING CHANGE: footer. Breaking changes must remain explicit during major version zero development.

Examples:

feat: add explicit world lifecycle
feat(core): add semantic greeting action
refactor(core)!: replace world initialisation contract
test: cover rejected time values
docs: define headless release architecture

Agents do not commit or push. Commit planning is defined in agent rules. Documentation under docs/ is never included in a commit plan.

13. Definition of done

A code change is done only when:

  1. it satisfies the assigned PBI without unapproved scope expansion
  2. it follows the specification and active architecture
  3. ownership and failure behaviour are explicit
  4. tests are added or updated
  5. relevant checks are run or reported as unavailable
  6. affected documentation is updated
  7. no unsupported platform claim is introduced
  8. no unrelated rewrite is included
  9. the result is reviewable