Files
dusk/.claude/style.md
T
2026-06-18 14:59:21 -05:00

8.6 KiB

Coding Style

All source is C11. Everything below is derived from the existing codebase — match it exactly.


File structure

Headers (.h)

/**
 * Copyright (c) 2026 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#pragma once
#include "direct/dependency.h"
  • #pragma once always, never #ifndef guards.
  • No blank line between the license block and #pragma once.
  • One blank line between #pragma once and the first #include.

Sources (.c)

/**
 * Copyright (c) 2026 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "thisfile.h"
#include "assert/assert.h"
#include "util/memory.h"
  • First include is always the matching .h for this .c file.
  • Remaining includes follow with no separator unless logically grouped (then one blank line between groups — see Include order).

Include order

In .c files, include in this order with a blank line between each group:

  1. The matching header (e.g. #include "entity.h")
  2. Core utilities (assert/assert.h, util/memory.h, util/math.h, etc.)
  3. Engine subsystems (display/..., input/..., etc.)
  4. Domain subsystems (rpg/..., scene/..., etc.)

In .h files, only include what the header directly requires. Never include more than necessary to make the type definitions in that header compile.

All include paths are relative to src/dusk/ (the root include directory). Use the full path:

#include "rpg/overworld/map.h"   // correct
#include "map.h"                  // wrong

Line length

80-character limit. Break before it, not after.

Multi-parameter function signatures break one param per line, with 2-space indent, closing ) on its own line before {:

errorret_t textureInit(
  texture_t *texture,
  const int32_t width,
  const int32_t height,
  const textureformat_t format,
  const texturedata_t data
) {

Same rule for calls that don't fit on one line:

assertTrue(
  data.paletted.palette->count ==
  mathNextPowTwo(data.paletted.palette->count),
  "Palette color count must be a power of 2"
);

Indentation and spacing

  • 2 spaces per indent level. No tabs.
  • No space between a control keyword and its (:
    if(x) {        // correct
    if (x) {       // wrong
    
  • No space between a function name and its ( in either declarations or calls.
  • Opening brace on the same line:
    void entityUpdate(entity_t *entity) {
    if(x) {
    for(int i = 0; i < n; i++) {
    
  • Closing brace always on its own line, except } else { and } while(...).
  • One blank line between function definitions in .c files.
  • No trailing whitespace.

Naming

Kind Convention Example
Types (struct/union/typedef) snake_case_t entity_t, worldpos_t
Struct tags struct name_s struct entity_s
Union tags union name_u union texturedata_u
Enum tags (when typedef'd separately) name_enum_t entitytype_enum_t
Functions subsystemVerb (camelCase, noun-first) entityInit, mapGetTile
Macro constants UPPER_SNAKE_CASE CHUNK_WIDTH, FIXED_ONE
Function-like macros camelCase (same as functions) errorThrow, assertNotNull
Global subsystem instances UPPER_SNAKE_CASE ENGINE, MAP, ENTITIES
Local variables camelCase tileNew, spriteCount
Parameters camelCase texture, worldPos

Subsystem prefix always comes first in function names: textureInit, shaderBind, spriteBatchFlush. The verb describes the action: Init, Update, Dispose, Get, Set, Is, etc.


Typedefs

Structs

typedef struct {
  uint8_t id;
  entitytype_t type;
} entity_t;

Use a named tag (struct entity_s) only when forward declaration is required:

typedef struct entity_s {
  // ...
} entity_t;

Unions

typedef union texturedata_u {
  struct {
    uint8_t *indices;
    palette_t *palette;
  } paletted;
  color_t *rgbaColors;
} texturedata_t;

Enums

When the enum values need to be a compact integer (common for arrays and flags), declare the enum separately and typedef an integer type:

typedef enum {
  ENTITY_TYPE_NULL,
  ENTITY_TYPE_PLAYER,
  ENTITY_TYPE_NPC,
  ENTITY_TYPE_COUNT
} entitytype_enum_t;

typedef uint8_t entitytype_t;  // actual type used everywhere

Always include _NULL as the first value (zero) and _COUNT as the last value.


#define constants

All-caps, underscores. Wrap multi-token expressions in parentheses:

#define CHUNK_WIDTH 16
#define CHUNK_HEIGHT CHUNK_WIDTH
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH)

Multi-line macros: backslash continuation, body indented 2 spaces, closing line has no backslash:

#define errorThrow(message, ...) \
  return errorThrowImpl(\
    &ERROR_STATE, ERROR_NOT_OK, __FILE__, __func__, __LINE__, (message), \
    ##__VA_ARGS__ \
  )

const usage

Mark every pointer and value parameter const unless the function modifies it:

void entityTurn(entity_t *entity, const entitydir_t direction);
errorret_t textureInit(texture_t *texture, const int32_t width, ...);

entity_t *entity is non-const because the function writes to it; direction is const because it is read-only.


void in no-argument functions

Use (void) in definitions and declarations of zero-parameter functions:

errorret_t engineUpdate(void);
errorret_t spriteBatchFlush(void);

Global subsystem state

Each subsystem exposes a single global instance declared extern in the header and defined (once) in the .c file:

// entity.h
extern entity_t ENTITIES[ENTITY_COUNT];

// entity.c
entity_t ENTITIES[ENTITY_COUNT];

Never define a subsystem global as static in a header.


Assertions

Place assertions at the very top of a function, before any logic:

void entityInit(entity_t *entity, const entitytype_t type) {
  assertNotNull(entity, "Entity pointer cannot be NULL");
  assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
  // ... actual logic
}

Available assertion macros (from assert/assert.h):

  • assertNotNull(ptr, msg)
  • assertNull(ptr, msg)
  • assertTrue(expr, msg)
  • assertFalse(expr, msg)
  • assertUnreachable(msg)
  • assertStringEqual(a, b, msg)
  • assertIsMainThread(msg) / assertNotMainThread(msg)

Error handling style

Functions that can fail return errorret_t. Three patterns:

// Propagate a child call's failure and return from this function:
errorChain(someCall());

// Return success:
errorOk();

// Return failure:
errorThrow("Descriptive message %s", variable);

errorChain is used inline — do not capture the result first:

errorChain(textureInitPlatform(texture, width, height, format, data));  // correct
errorret_t r = textureInitPlatform(...); errorChain(r);                 // wrong

Struct initialization

Use C99 designated initializers for any struct literal with more than one field:

static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
  [ENTITY_TYPE_NULL] = { NULL },

  [ENTITY_TYPE_PLAYER] = {
    .init = playerInit,
    .movement = playerInput
  },
};
shadermaterial_t material = {
  .unlit = {
    .color = COLOR_WHITE,
    .texture = NULL
  }
};

Fixed-size array iteration

Prefer pointer arithmetic with do/while over index loops for iterating through fixed global arrays:

entity_t *ent = ENTITIES;
do {
  if(ent->type == ENTITY_TYPE_NULL) continue;
  // ...
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);

Use for loops when an index variable is actually needed.


Comments

Comments explain why, not what. One short inline comment is fine; multi-line block comments for non-obvious invariants only.

// Walking up a ramp — only the direction the ramp faces is valid.
if(tileIsRamp(tileCurrent) && ...) {

Section labels inside long functions are acceptable:

// Chunks
{
  ...
}

// Entities
{
  ...
}

Doc comments on public functions use Javadoc style with @param / @return:

/**
 * Gets the tile at the given world position.
 *
 * @param position The world position.
 * @return The tile at that position, or TILE_NULL if the chunk is unloaded.
 */
tile_t mapGetTile(const worldpos_t position);

Platform-conditional code

Use the DUSK_* compile-definition macros set by cmake/targets/<target>.cmake:

#ifdef DUSK_THREAD_PTHREAD
  #include "thread/thread.h"
  extern pthread_t ASSERT_MAIN_THREAD_ID;
#endif

Never use #ifdef __linux__, #ifdef _WIN32, etc. directly — go through the engine macros.