# Dusk -- Claude Code rules ## About Dusk Dusk is a pure C game and game engine. There is no C++ anywhere in the codebase. The engine is built around a data-oriented Entity Component System (ECS) and is designed for heavy optimization across a wide range of hardware targets, including platforms with very limited RAM and CPU. **Current and planned platforms:** Linux, Windows, macOS, Sony PSP, PlayStation Vita, Nintendo GameCube, Nintendo Wii. Additional platforms will be added over time. GameCube and Wii are collectively referred to as the **Dolphin** targets throughout the codebase and docs. **Build system:** CMake exclusively. Every source subdirectory owns its own `CMakeLists.txt`. **Architecture:** Entity Component System (ECS) as the primary pattern. All game objects are entities; behaviour and state are attached via components. No inheritance hierarchies -- favour composition. **Optimization:** Performance is a first-class constraint, not an afterthought. The engine must run well on hardware as constrained as the GameCube (16 MB main RAM, 485 MHz PowerPC) and PSP (32 MB RAM, 333 MHz MIPS). Every design and implementation decision should consider the most constrained target. **Coding style:** All C code must strictly follow the project style rules documented in the [Coding style](#coding-style) section below. Deviations are not acceptable. ### Further reading Detailed documentation on specific topics lives in `.claude/`: | Topic | File | |-------|------| | Platform overview, capability macros, quick comparison | `.claude/platforms.md` | | Platform -- Linux and Knulli | `.claude/platform-linux.md` | | Platform -- Sony PSP | `.claude/platform-psp.md` | | Platform -- PlayStation Vita | `.claude/platform-vita.md` | | Platform -- GameCube and Wii (Dolphin) | `.claude/platform-dolphin.md` | | Platform -- Windows (planned) | `.claude/platform-windows.md` | | Platform -- macOS (planned) | `.claude/platform-macos.md` | | ECS architecture and conventions | `.claude/ecs.md` | | CMake build system and toolchain setup | `.claude/build.md` | | Optimization guidelines and platform budgets | `.claude/optimization.md` | | Test infrastructure and assertion macros | `.claude/tests.md` | | Error handling system (`errorret_t`, macros) | `.claude/errors.md` | | Asset system (loading, caching, loaders) | `.claude/assets.md` | | Threading (`thread_t`, mutex, thread-local) | `.claude/threading.md` | | Input system (actions, buttons, platforms) | `.claude/input.md` | | Physics simulation and collision shapes | `.claude/physics.md` | | Event system (pub/sub) | `.claude/events.md` | | Locale / localisation system | `.claude/locale.md` | | Time system (fixed/dynamic, epoch) | `.claude/time.md` | | Network system (per-platform status) | `.claude/network.md` | | Utility library (memory, string, math, endian, ref) | `.claude/util.md` | | Script system (JerryScript, module proto API) | `.claude/script.md` | | Script async promises (`scriptpromisepend_t`) | `.claude/script-promises.md` | | Engine main loop, system platform API, log | `.claude/engine.md` | | Save system (multi-slot, platform storage) | `.claude/save.md` | | Animation (keyframes, easing functions) | `.claude/animation.md` | | Display (index) | `.claude/display.md` | | Display -- screen, framebuffer, size modes | `.claude/display-core.md` | | Display -- texture, tileset, font | `.claude/display-texture.md` | | Display -- shader, material, display state | `.claude/display-shader.md` | | Display -- mesh, vertex format, primitives | `.claude/display-mesh.md` | | Display -- SpriteBatch (2D quad renderer) | `.claude/display-spritebatch.md` | | Display -- text rendering, FONT_DEFAULT | `.claude/display-text.md` | | Display -- color types, macros, constants | `.claude/display-color.md` | | UI system (frame, textbox, fullbox, loading, FPS) | `.claude/ui.md` | | Console (debug overlay, POSIX stdin mode) | `.claude/console.md` | | Scene system (lifecycle, render pipeline, JS hooks) | `.claude/scene.md` | --- ## 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. - Do not use `static` or `inline` on **functions**. Every function, including internal helpers, must be declared in the matching `.h` and defined in the `.c` file. Internal helpers belong near the bottom of the `.c` file, not at the top with a `static` qualifier. `static` and `inline` on functions are only appropriate when the function body is written directly inside a `.h` file. `static` on **variables** (file-scope state) is fine and expected. ### 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 See `.claude/build.md` for extended CMake conventions, platform toolchain setup, and adding platform-conditional sources. 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 See `.claude/platforms.md` for the full platform table (including planned Windows / macOS targets), capability macros, toolchain setup, and endianness notes. ### 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 entity component See `.claude/ecs.md` for ECS design rules, component categories, and entity lifecycle details. 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 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 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 ### ASCII only Source files (`.c`, `.h`, `.js`) must contain only ASCII characters (U+0000–U+007F). Non-ASCII characters are banned even in comments and string literals. Use ASCII-only substitutes instead: - `--` or `-` instead of `—` (em dash) - `->` instead of `→` (arrow) - `x` or `*` instead of `×` (multiplication) Only non-script asset files (e.g. `.po` locale files) may contain non-ASCII text. ### 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 - Do not use section dividers (`/* ---- ... ---- */`). Just let the functions follow one another with a single blank line between them. - 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`.