When something renders incorrectly, we need to follow the data from its file to the code that puts it on screen, and every extra wrapper or duplicate record is one more thing to open on the way. Most of our style rules come from wanting to make that easier.

Long identifiers

A function’s name is often the only part of it visible in a call stack. prepare tells us very little there, while translate_d3d9_shader_bytecode_to_spirv tells us what the function takes and what it produces.

The words still need to mean something, so “manager” or “processor” doesn’t help if we still have to open the file to find out what it manages. Modules work the same way, with a directory for each part of the game instead of a growing pile of unrelated code in utils.

Terminal tree of the habitat plugin's modules, including habitat_region_derivation, habitat_terrain_summary_refresh and habitat_topology_cell_queries, each with a matching tests module.
Modules in OpenZT2’s habitat plugin

Data layout and comments

We like the layout of old C headers and languages such as Odin: related fields together, with types, identifiers and short comments lined up. Here’s a small Odin example:

ODIN
// Font, font texture and glyph-array data
Font :: struct {
    base_size:      i32,           // Default character height
    glyph_count:    i32,
    glyph_padding:  i32,           // Padding in the texture atlas
    texture:        Texture2D,
    rectangles:     [^]Rectangle,  // Glyph rectangles
    glyphs:         [^]GlyphInfo,
}

Rust’s standard formatter doesn’t preserve column alignment, so Rust code keeps its normal formatting, but the arrangement still holds: data definitions belong together, and the procedures that work on them go in separate modules.

Comments explain a field’s units, constraints on its value or a tricky algorithm, and stay short unless the algorithm needs more. A comment that repeats a field’s identifier only adds noise.

Shared data

Bevy already owns the live game world, so other parts of the program refer to its data directly. A second copy would have to be kept in sync with the first, which means more work and more ways for the game to go wrong.

We’re cautious about wrappers for the same reason. Some are necessary, particularly at a library boundary, but if a type just renames the fields of another type, or a function only forwards a call, we probably don’t need it.

Contributing

These preferences are written down so contributors don’t have to discover them during review, which leaves review for whether the change works. If a rule makes a particular change awkward, bring up the example in the pull request and we can talk it through. The guide also asks contributors to say when they’ve used AI, and to confirm that a person has reviewed the submitted code and takes responsibility for it.