# Dusk — Claude Code rules ## File headers Every C, H, and JS file starts with: ```c /** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ ``` JS files use `//` comment style instead. --- ## C conventions ### Types Always use the project-defined aliases instead of bare C primitives: | Use | Not | |-----------|--------------| | `bool_t` | `bool` | | `int_t` | `int` | | `float_t` | `float` | | `char_t` | `char` | Use `uint8_t`, `uint16_t`, `int32_t`, etc. for fixed-width integers. All struct and enum types end in `_t` (`animation_t`, `errorret_t`, …). ### Naming - **Functions** — snake_case, prefixed with their module: `assetLock()`, `entityPositionInit()`, `moduleAssetBatchCtor()` - **Struct fields** — camelCase: `keyframeCount`, `localPosition` - **Macros / constants** — UPPER_SNAKE_CASE: `ENTITY_ID_INVALID`, `ERROR_OK`, `COMPONENT_TYPE_COUNT` - **Files** — snake_case matching the primary type: `entityposition.c`, `moduleassetbatch.c` ### Header files (`.h`) - Use `#pragma once` — no include guards. - Declare every public function, `#define`, and `extern` global. - Write a JSDoc block (`/** … */`) above every declaration explaining purpose, `@param`s, and `@returns`. - Only include headers that the `.h` file itself strictly requires for the types it exposes. Move everything else to the `.c` file. Do not use forward declarations as a workaround — use the real include in the `.c` file instead. ### Implementation files (`.c`) - Contain function bodies only; no declarations. - Pull in whatever additional includes the implementation needs. ### Formatting - Hard-wrap all lines at **80 characters**. ### Error handling Return `errorret_t` from fallible functions. Use these macros: ```c errorOk(); // return success errorThrow("msg %d", val); // return failure with message errorChain(someCall()); // propagate failure, continue on success errorIsOk(ret) / errorIsNotOk(ret) // test a result errorCatch(ret); // handle + free an error ``` Never return raw error codes or use `errno` for in-engine errors. ### Memory Use the project allocator — never raw `malloc`/`free`: ```c memoryAllocate(size) // allocate memoryFree(ptr) // free memoryZero(dest, size) // zero a block memoryCopy(dest, src, size) // copy ``` ### Asserts Prefer specific assert macros over bare `assert()`: ```c assertNotNull(ptr, "msg"); assertTrue(cond, "msg"); assertFalse(cond, "msg"); assertUnreachable("msg"); assertIsMainThread("msg"); ``` --- ## Build system Each subdirectory has its own `CMakeLists.txt` that adds sources with: ```cmake target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC myfile.c ) ``` Never add source files to the root `CMakeLists.txt` directly. --- ## Platform support ### Targets Set `DUSK_TARGET_SYSTEM` at CMake configure time to select a platform: | `DUSK_TARGET_SYSTEM` | Macro defined | Platform | |----------------------|-------------------|------------------| | `linux` | `DUSK_LINUX` | Linux desktop | | `knulli` | `DUSK_KNULLI` | Knulli (handheld)| | `psp` | `DUSK_PSP` | Sony PSP | | `vita` | `DUSK_VITA` | PlayStation Vita | | `gamecube` | `DUSK_GAMECUBE` | Nintendo GameCube| | `wii` | `DUSK_WII` | Nintendo Wii | ### Layer structure ``` src/dusk/ core, platform-agnostic game logic src/duskgl/ OpenGL abstraction (Linux, Knulli, PSP, Vita) src/dusksdl2/ SDL2 window + input (Linux, Knulli, PSP, Vita) src/dusklinux/ Linux + Knulli platform impl src/duskpsp/ PSP platform impl src/duskvita/ Vita platform impl src/duskdolphin/ GameCube / Wii platform impl (no SDL2/OpenGL) ``` Dolphin is the only target that bypasses SDL2 and OpenGL entirely — it uses native GameCube/Wii rendering and input APIs. ### Platform guards Use the compile-time macros for platform-specific code: ```c #ifdef DUSK_PSP // PSP-only path #elif defined(DUSK_GAMECUBE) || defined(DUSK_WII) // GameCube / Wii path #else // Generic / Linux fallback #endif ``` Additional capability macros set per-target: `DUSK_SDL2`, `DUSK_OPENGL`, `DUSK_OPENGL_ES`, `DUSK_OPENGL_LEGACY`, `DUSK_INPUT_GAMEPAD`, `DUSK_INPUT_KEYBOARD`, `DUSK_INPUT_POINTER`, `DUSK_PLATFORM_ENDIAN_BIG` / `DUSK_PLATFORM_ENDIAN_LITTLE`. ### Abstraction pattern Platform-specific implementations are wired in via `#define` macros in each platform's `displayplatform.h` / `inputplatform.h` etc., which the core calls through. Functions that a platform does not support are simply left undefined — the core guards calls with `#ifdef`. ### Adding platform-specific code - Put it under `src/dusk/` in the matching subsystem folder. - Gate any core call-site with the appropriate `#ifdef DUSK_` or capability macro. - Keep the `src/dusk/` core free of platform ifdefs — delegate through the platform header macros instead. --- ## Adding a new asset loader type 1. Add an enum value to `assetloadertype_t` (before `_COUNT`) in `src/dusk/asset/loader/assetloader.h`. 2. Add fields to the input/loading/output unions in `assetloader.h`. 3. Implement `assetXxxLoaderSync`, `assetXxxLoaderAsync`, and `assetXxxDispose` in a new `src/dusk/asset/loader/xxx/` directory. 4. Register the three callbacks in `ASSET_LOADER_CALLBACKS[]` in `src/dusk/asset/loader/assetloader.c`. 5. If user-facing, create a JS module (see below) and a `.d.ts` file. --- ## Adding a new entity component 1. Create `src/dusk/entity/component//entityMyComp.h/.c` with struct `entityMyComp_t`, `entityMyCompInit()`, and optionally `entityMyCompDispose()`. 2. Add the include to `src/dusk/entity/componentlist.h` header block. 3. Add a row to `src/dusk/entity/componentlist.h`: ```c X(MYCOMP, entityMyComp_t, myComp, entityMyCompInit, NULL, NULL) ``` This auto-generates the enum, union field, and definition entry. 4. If JS-facing, create the script module and `.d.ts` (see below). --- ## Adding a new script (JS) module 1. Create `src/dusk/script/module//moduleMyMod.h/.c`. - Declare `extern scriptproto_t MODULE_MYMOD_PROTO;` in the header. - Use `moduleBaseFunction(name)` to define JS-callable functions. - Register props/funcs in `moduleMyModInit()` with `scriptProtoDefineProp` / `scriptProtoDefineFunc` / `scriptProtoDefineStaticFunc`. 2. `#include` the header in `src/dusk/script/module/modulelist.c` and call `moduleMyModInit()` in `moduleListInit()` (and `Dispose` in `moduleListDispose()`). 3. For component modules also register in `src/dusk/script/module/entity/component/modulecomponentlist.c` so `entity.add()` returns the typed wrapper. 4. Create `types//mymod.d.ts` and add a `/// ` line to `types/index.d.ts`. --- ## Script module type declarations Whenever a `src/dusk/script/module/**/*.c` file is created or modified, check whether the corresponding `types/**/*.d.ts` needs updating and apply any changes before finishing the task. --- ## JavaScript (asset scripts) - Use `var` for module-level state; `const` for values that never change. - Always use semicolons. - Scene objects are plain objects (`var scene = {}`) with assigned methods. - Export via `module.exports = scene`. - Async scene init should use `async function` and `await`. --- ## Coding style ### Indentation 2 spaces. No tabs. ### Keyword and operator spacing No space between a keyword or function name and its opening parenthesis: ```c if(!ptr) return; for(uint8_t i = 0; i < count; i++) { while(entry->state != DONE) { switch(type) { sizeof(assetbatch_t) memoryZero(ptr, size) ``` Spaces around all binary operators and after every comma: ```c pos->flags |= ENTITY_POSITION_FLAG_WORLD_DIRTY; (size_t)end - (size_t)start foo(a, b, c) ``` ### Braces Opening brace on the **same line** as the statement (K&R style) for all constructs — functions, `if`, `else`, `for`, `while`, `switch`: ```c void assetEntryLock(assetentry_t *entry) { ... } if(dirty) { ... } else { ... } ``` ### Guard returns Short guards go on one line with no braces: ```c if(!ptr) return; if(!b || !b->batch) return jerry_undefined(); if(!(flags & DIRTY)) return; ``` ### Blank lines - One blank line between functions; no blank line at the start or end of a function body. - One blank line between logical blocks inside a function body. - No trailing blank lines at the end of a file. ### Pointer placement `*` is attached to the variable name, not the type: ```c assetentry_t *entry const char_t *name void *ptr uint8_t *d = (uint8_t *)dest; ``` ### Casts Space between cast and operand: ```c (assetbatch_t *)user (uint8_t *)dest (textureformat_t)v ``` ### Return No parentheses around the return value: ```c return ptr; return MEMORY_POINTERS_IN_USE; ``` ### switch / case `case` indented 2 spaces from `switch`; body indented 2 more from `case`: ```c switch(type) { case ASSET_LOADER_TYPE_TEXTURE: descs[i].input.texture = (textureformat_t)v; break; default: break; } ``` ### Multi-line function signatures When parameters don't fit on one line, put each on its own line indented 2 spaces; the closing `) {` (definition) or `);` (declaration) goes on its own line at column 0: ```c void assetEntryInit( assetentry_t *entry, const char_t *name, const assetloadertype_t type, assetloaderinput_t *input ) { errorret_t memoryCompare( const void *a, const void *b, const size_t size ); ``` ### Structs and enums Anonymous inner struct or enum with a `typedef`, `_t` suffix, closing brace and name on the same line: ```c typedef struct { errorcode_t code; char_t *message; } errorstate_t; typedef enum { ASSET_LOADER_TYPE_NULL, ASSET_LOADER_TYPE_COUNT } assetloadertype_t; ``` ### Designated initialisers Spaces inside braces; `.field = value`: ```c jsassetentry_t e = { .entry = entry }; assetbatchloadedpend_t init = { .batch = batch }; ``` ### Ternary operator Spaces around `?` and `:`: ```c const float val = psx > 0.0f ? pt[0][0] / psx : 0.0f; ``` ### const placement `const` before the type, `*` attached to the variable: ```c const char_t *name const void *src const size_t size ``` ### Comments in `.c` files - Block comments that describe a section use the divider style: ```c /* ---- Public API ---- */ ``` - Multi-line explanatory comments inside function bodies use `//` lines: ```c // Script modules are freed; orphaned JS wrapper objects now get GC'd // so their finalizers fire before assetDispose() checks ref counts. jerry_heap_gc(JERRY_GC_PRESSURE_HIGH); ``` - Do not use `/* */` for inline or inline-block comments inside `.c` function bodies. ### Comments in `.h` files Every public declaration gets a Javadoc block (`/** … */`) with `@param` and `@returns` where relevant. Keep it on the lines immediately above the declaration with no blank line in between. --- ## Tests - Tests live in `test/` mirroring `src/dusk/` structure. - Use cmocka; include `dusktest.h`. - Test functions: `static void test_something(void **state)`. - After each test, assert `memoryGetAllocatedCount() == 0` to catch leaks. - Build with `-DDUSK_BUILD_TESTS=ON`.