Code Standards (Normative)¶
Purpose and scope (what it owns / what it does NOT own)¶
This document defines Zireael’s coding standards that are not purely “safety rules”:
- naming conventions and module structure
- documentation/comment requirements
- assert usage policy
- macro/global-state policy
It complements (and must not contradict):
docs/SAFETY_RULESET.md(LOCKED safety + determinism + concurrency rules)docs/LIBC_POLICY.md(LOCKED libc policy)
Readability and structure¶
These rules exist to keep the engine boring, readable, and deterministic. Favor obvious code over clever code.
This document uses MUST / MUST NOT / SHOULD language on purpose.
Readability first¶
- Code MUST optimize for maintainability by humans and future LLM contributors.
- Micro-optimizations MUST NOT be accepted if they materially harm clarity.
- Dense expressions SHOULD be split into named intermediate variables.
Function size and shape¶
- Functions SHOULD be 20–40 lines and MUST generally be < 50 lines.
- A function MUST do one thing. If you need comments to explain the structure of the function, it is too big.
- Prefer “parse/validate/execute” style decomposition over multi-purpose helpers.
Comments policy (Why > What)¶
- Comments MUST explain:
- why something is done
- what invariant is being protected
- what assumption the code relies on (e.g., “validated view”)
- Comments MUST NOT restate code line-by-line.
- Tricky parsing/framing logic SHOULD be annotated with brief “shape” comments (header → ranges → spans → stream).
Comment style (format and tone)¶
- Prefer short, structured block comments for non-trivial reasoning (typically 5–20 lines).
- Use
/* ... */for single-line comments and multi-line blocks; avoid//in engine code. - Multi-line blocks SHOULD follow the aligned
*style when the comment spans more than ~2 lines:
/*
* What this block does (high-level).
*
* Why it exists (invariant, deterministic policy, or subtle edge case).
* Any important caller/callee contract notes.
*/
- Comments SHOULD be factual and specific; avoid generic filler (no “AI-sounding” commentary).
Strategic comments (where to add them)¶
Add comments only where they carry real information that is not obvious from the code:
- Algorithm shape and invariants (e.g., ring buffer head/tail layouts, wrap-around behavior).
- Deterministic policy notes (e.g., invalid UTF-8 replacement behavior, tie-break rules).
- State machines (e.g., grapheme boundary rule state tracking like GB11).
- Long functions: add short section markers when a function has multiple conceptual phases (reset → attrs → colors → finalize).
- Defensive NULL checks: only when non-obvious (e.g., "cleanup path accepts NULL", "lock is no-op on NULL for error paths").
Function-level comments (when required)¶
Non-trivial functions MUST have a brief comment at the top explaining what the function does (not how). This applies to:
- Public API functions (
zr_*that appear in headers and are called by other modules or callers). - Functions > 20 lines that do something non-obvious from the name alone.
- Functions with subtle behavior (e.g., coalescing, tie-breaking, capability downgrading).
- Internal helpers that are called from multiple places or have non-obvious contracts.
Function-level comments SHOULD be 1–3 lines and answer: "What does this function do and why would I call it?"
Example:
/* Map 24-bit RGB to nearest xterm 256-color index, comparing both
* the 6x6x6 color cube and grayscale ramp to find the best match. */
static uint8_t zr_rgb_to_xterm256(uint32_t rgb) {
Functions that do NOT need function-level comments:
- Trivial one-liners (e.g.,
zr_rgb_r(),zr_style_default()). - Simple getters/setters where the name is self-explanatory.
- Static helpers under ~10 lines with obvious intent from the name.
File header comments (required)¶
Every .c/.h file MUST start with a brief “what/why” header.
Example:
/*
src/core/zr_drawlist.c — Drawlist validator and executor.
Why: Validates wrapper-provided bytes and executes drawing into the framebuffer safely.
Invariants:
- No OOB reads; all offsets/sizes validated before access.
- No partial effects: validate fully before mutating the framebuffer.
Failure modes:
- Invalid format -> ZR_ERR_FORMAT / ZR_ERR_UNSUPPORTED.
- Caps exceeded -> ZR_ERR_LIMIT.
*/
Public API (function signatures and types; include header file names)¶
Public-facing code conventions (applies repo-wide):
- filename conventions, header layout, include discipline
- stable prefixes for public and internal symbols
Invariants (explicit MUST-always-hold statements)¶
Naming (normative)¶
Function prefixes MUST be explicit and stable:
engine_*— public ABI and orchestrationdl_*— drawlist parsing/validation/executionfb_*— framebuffer operationsdiff_*— diff rendererutf8_*/grapheme_*/width_*/wrap_*— Unicode primitivesplat_*— platform interface and backendszr_*— shared base types/utilities
Names MUST describe intent:
- Functions:
arena_alloc_aligned(),dl_validate_header_v1(),fb_cell_index() - Variables:
write_offset,bytes_remaining,span_index_bytes
Avoid one-letter names except for trivial loops (i, j) or small, obvious scopes.
Type naming:
- typedef’d structs/enums MUST use
*_tsuffix. - ABI-visible types MUST use fixed-width integers (
uint32_t,int32_t, etc.). - Public ABI surfaces MUST keep stable names and signatures; refactors MUST NOT rename public API.
Module structure (normative)¶
- Code MUST be split into modules with single responsibility (
foo.h+foo.c). - Headers MUST minimize includes and MUST NOT leak platform headers across boundaries.
- OS types MUST NOT cross the platform boundary (use POD + opaque handles).
Ownership, lifetimes, and out-params (normative)¶
- Every struct that contains pointers MUST document:
- who owns the pointed-to memory
- how long it must remain valid
- whether the pointer can be NULL
- The engine MUST NOT return heap pointers that require the caller to
free(). - Out-parameters MUST be either:
- fully written on success and left in a documented safe state on failure, or
- explicitly zeroed at the start of the function (before any early returns).
Pointer arithmetic and bounds checks (normative)¶
- Pointer arithmetic MUST be written in steps with checked math:
- compute offsets with
zr_checked_* - validate ranges before creating derived pointers
- compute offsets with
- Prefer
if (!ptr)/if (ptr)for NULL checks in simple conditions; keep explicit== NULL/!= NULLonly when it materially improves readability in complex boolean expressions. - Avoid “clever” casts. Unaligned reads MUST use byte loads or
memcpyhelpers (no type-punning). - Any non-obvious boundary check MUST have a brief comment explaining the invariant being protected.
Macro policy (normative)¶
- Macro-heavy “generic programming” is discouraged.
- Macros are allowed only for:
- assertions
- small constants/caps
- carefully-audited
ZR_MIN/ZR_MAX(if used)
- Macros MUST NOT hide allocations or complex control flow.
Global state policy (normative)¶
- Global mutable state is FORBIDDEN (except immutable tables in Unicode/data).
Assertions (normative)¶
- Asserts are for internal invariants and programmer errors only.
- Asserts MUST NOT be used to validate untrusted input (drawlists, platform input bytes).
Failure modes & error codes¶
Coding standard violations are treated as bugs and are handled via:
- compile failures (warnings-as-errors in CI where feasible)
- test failures
- debug assertions
| Condition | Required behavior | Return code |
|---|---|---|
| Untrusted input validation done via assert | Treat as bug; refactor to bounds-checked error returns | n/a |
| Module includes OS headers in core/unicode/util | Treat as bug; CI guardrail should fail | n/a |
Error handling and “No Partial Effects”¶
- Success is
ZR_OK == 0. Failures are negativeZR_ERR_*codes. - Failure paths MUST be obvious:
- use early returns for simple functions
- use
goto cleanupfor multi-resource functions
- Code MUST NOT have hidden side effects on failure:
- validate fully before mutating state when possible
- do not partially write output buffers unless the API explicitly allows it
Example cleanup pattern:
zr_result_t rc = ZR_OK;
resource_t* r = NULL;
r = alloc_resource();
if (!r) { rc = ZR_ERR_OOM; goto cleanup; }
cleanup:
free_resource(r);
return rc;
Performance notes (hot paths, allocation rules, complexity targets)¶
- Keep hot path code simple and auditable:
- small focused functions
- minimal branching where possible
- avoid hidden allocations
- Prefer contiguous buffers and explicit bounds checks in parsers.
Concurrency rules (what can be called from which thread; locking)¶
Concurrency rules are locked in docs/SAFETY_RULESET.md.
Coding standards requirement:
- Thread-affinity and thread-safety requirements MUST be documented in each module header for any public/engine entrypoints.
Deterministic behavior guarantees¶
Determinism requirements are locked in docs/SAFETY_RULESET.md.
Coding standards requirement:
- Any behavior change that affects:
- binary formats
- output byte streams
- event ordering/coalescing must update the corresponding module doc and tests/fixtures in the same change.
Test plan¶
- Unit tests and fuzz targets must cover parsers and invariants.
- Golden tests must pin output byte streams and prevent regressions.
See docs/modules/TESTING_GOLDENS_FUZZ_INTEGRATION.md.