Coding guidelines¶
BattleTime code should read plainly. Make it readable by how it is named and laid out, never by cutting features.
Names¶
- Use full snake_case words:
duration_in_milliseconds,motor_pin,first_memory_row. - Type parameters are full words too:
MotorPin,PwmChannel,DisplayInterface. - No single letters, abbreviations (
di,rst,y) or tuple fields likeself.0. Use a small named struct instead of a tuple. - Put units in the name when there is one:
_in_milliseconds,_percent,_rows. - Give magic numbers a name:
const SET_SCROLL_START: u8 = 0x37;.
Layout¶
Split each file into sections, each under a banner:
// ============================================================================
// Patterns
// ============================================================================
Inside a function, leave a blank line between setup, action and checks.
Prefer a standard helper over a hand-rolled trick, for example embassy_futures::block_on or
core::mem::take.
Comments¶
- Let names carry the meaning, so most items need no comment.
- A comment is one sentence on one line, in plain words.
- Only the overview at the top of a module may run longer, to say what the module is for.
- A comment says what the code is and does now, never what it used to be; one short sentence may name a hazard, such as why a read of address 0 uses assembly.
- Explanations and measurements go in
docs/, not in the code.
Drivers¶
- Each driver is its own
no_stdcrate underdrivers/, namedbattletime-<part>. - A driver talks to hardware only through the
embedded-haltraits, never a chip crate, so it can run in tests and the simulator. It may build on a portable crate such asmipidsi. The only exceptions are parts inside the nRF52832 itself, whichembedded-halhas no traits for: the Bluetooth radio keeps all its register access in one file behind a small trait (bluetooth.md), andbattletime-battery-readingdrives the SAADC for programs with no executor. - A new driver gets a simulated part in
simulator/and a line in simulator.md. - Tests live in
src/tests.rsbesidesrc/lib.rs, or insrc/<module>_tests.rsbeside a module, and drive the code through fake hardware that records what it was sent. - Name tests after the behaviour they check:
splits_where_memory_wraps,cancelling_a_vibration_turns_the_motor_off.
Apps and shared parts¶
- Apps are assembled from shared parts that live in the firmware and the shared library; the app itself holds only what is unique to it. RAM is the scarcest resource, and shared parts cost none. app-runtime.md gives the rules.
- Use the shared parts before writing your own, and move anything a second app needs into the shared library, written generically.
- Build a composite widget with its own logic, or heavy work over a whole array, natively in the firmware rather than in app code.
- Never put an app or a face in the internal flash.
Tests and coverage¶
- Every change ships with tests for what it adds or changes, in the same commit.
- Run only the tests for the code you changed. CI runs the rest on every push and pull request:
every workspace's tests, clippy, the firmware builds, the emulator suites and
tools/check_coverage.sh, which fails below the coverage floors (the Rust one in the script, the TypeScript ones invite.config.ts). - When a change raises coverage, raise the floor in the same change, so it never slips back.
- App behaviour is tested in
harness/, with snapshots of each screen; see testing.md. - The firmware, bootloader and reloaders build only for the watch; keep their logic in workspace
crates where tests can reach it, and test the rest in the emulator suites under
tests/emulator.
Formatting and checks¶
- Run
cargo fmt -p <crate>for the crates you changed (maximum line width 110, fromrustfmt.toml), never a barecargo fmtover the whole workspace. - A change to firmware code also builds for the watch,
cargo build --release --target thumbv7em-none-eabihfinfirmware/, and keeps 2 KB of stack spare, measured withtools/measure_stack.py; a change to RAM or flash use updates memory-map.md.
Commits¶
Commit messages follow Conventional Commits:
<type>(<scope>): <description>, for example feat(display): ....