1 Commits

Author SHA1 Message Date
YourWishes 5919881568 Vita progress 2026-03-28 19:09:21 -05:00
705 changed files with 10929 additions and 30006 deletions
-74
View File
@@ -1,74 +0,0 @@
# Animation System
Source: `src/dusk/animation/`
Lightweight keyframe-based value interpolation using fixed-point math throughout.
---
## Easing (`animation/easing.h`)
`easingApply(type, t)` applies an easing function to a normalized time value `t ∈ [0, FIXED_ONE]` and returns the eased value in the same range.
All functions are also callable directly:
```c
fixed_t t = FIXED(0.5f);
fixed_t out = easingApply(EASING_IN_OUT_CUBIC, t);
```
Available easing types (all in `easingtype_t`):
| Enum value | Curve |
|---|---|
| `EASING_LINEAR` | straight line |
| `EASING_IN_SINE` / `OUT` / `IN_OUT` | sinusoidal |
| `EASING_IN_QUAD` / `OUT` / `IN_OUT` | quadratic |
| `EASING_IN_CUBIC` / `OUT` / `IN_OUT` | cubic |
| `EASING_IN_QUART` / `OUT` / `IN_OUT` | quartic |
| `EASING_IN_BACK` / `OUT` / `IN_OUT` | overshoots slightly |
`EASING_FUNCTIONS[EASING_COUNT]` is a table of `easingfn_t` function pointers for when you need to pick an easing at runtime without a switch.
---
## Keyframes (`animation/keyframe.h`)
```c
typedef struct {
fixed_t time; // time point this keyframe is at
fixed_t value; // output value at this time
easingtype_t easing; // easing to apply when interpolating toward the NEXT keyframe
} keyframe_t;
```
Keyframe arrays should be sorted ascending by `time`.
---
## Animation (`animation/animation.h`)
`animation_t` wraps a keyframe array and provides value lookup:
```c
keyframe_t frames[] = {
{ FIXED(0.0f), FIXED(0.0f), EASING_LINEAR },
{ FIXED(1.0f), FIXED(1.0f), EASING_IN_OUT_CUBIC },
{ FIXED(2.0f), FIXED(0.0f), EASING_LINEAR },
};
animation_t anim;
animationInit(&anim, frames, 3);
fixed_t value = animationGetValue(&anim, FIXED(0.75f)); // interpolated
```
`animationGetValue` finds the surrounding keyframes for the given `time`, computes the local `t` within that segment, applies the keyframe's easing, and linearly interpolates between the two keyframe values.
The animation does not own the keyframe array — it holds a pointer. Pass a static or long-lived array.
---
## Usage in the engine
Entity animations (`entityanim_t`) do NOT use this system — they use a simple countdown timer (`animTime`) and a state enum. The `animation_t` system is intended for property animation: UI transitions, camera easing, visual effects, anything that needs a time → value curve.
-81
View File
@@ -1,81 +0,0 @@
# Architecture
## Platform abstraction
Every subsystem that differs across platforms (display, input, asset loading, save, time, network, log) follows the same pattern:
1. `src/dusk/<subsystem>/<subsystem>platform.h` — included by the public header. Contains `#include "path/to/platform-specific-header.h"` resolved by the build system include path.
2. `src/dusk{platform}/<subsystem>/<subsystem>platform.h` — the actual platform-specific header included above (e.g. `src/duskgl/display/framebuffer/framebufferplatform.h`).
3. The shared header (`src/dusk/<subsystem>/<subsystem>.h`) `#error`s at compile time if the platform doesn't define the expected macros/types.
The active platform backends are selected by `DUSK_TARGET_SYSTEM` in CMake, which includes `cmake/targets/<system>.cmake`. That file sets compile definitions (`DUSK_LINUX`, `DUSK_SDL2`, `DUSK_OPENGL`, …) and links platform libraries.
Platform source directories:
- `src/duskgl/` — OpenGL rendering (used on Linux and as the GL layer for SDL2)
- `src/dusksdl2/` — SDL2 window/input/time (Linux desktop)
- `src/dusklinux/` — Linux filesystem/save/network
- `src/duskdolphin/` — GameCube & Wii (GX renderer, libogc)
- `src/duskpsp/` — PSP (GU renderer, PSPSDK)
- `src/duskvita/` — PS Vita
## Subsystem lifecycle
All subsystems follow `init → update (per frame) → dispose`. Engine initialization order matters and is centralized in `engine.c`:
```
systemInit → timeInit → consoleInit → inputInit → assetInit →
localeManagerInit → displayInit → uiInit → uiTextboxInit →
cutsceneInit → rpgInit → networkInit → sceneInit
```
Dispose runs in reverse. Each call uses `errorChain()` to propagate failures.
## Error handling
Functions that can fail return `errorret_t` (a code + pointer to thread-local error state). Three core macros:
```c
errorThrow("message %s", arg); // sets error, returns from current function
errorChain(someCall()); // if someCall() fails, propagates and returns
errorOk(); // returns success
```
Check with `errorIsOk(ret)` / `errorIsNotOk(ret)`. The error state carries file/function/line info for a stack-like trace.
## Fixed-point math
`fixed_t` is `int32_t` with Q24.8 format (8 fractional bits, ~0.004 resolution). Use it for all world/game values:
```c
fixed_t x = FIXED(1.5); // compile-time literal
fixed_t y = fixedFromI32(3); // runtime conversion
fixed_t z = fixedMul(x, y); // arithmetic
float_t f = fixedToFloat(z); // only where float is needed (e.g. GL uniforms)
```
## Code generation from CSV
Several subsystems define their data in CSV files and have corresponding Python tools that generate C headers at build time (via CMake `add_custom_command`):
| CSV | Tool | Output |
|-----|------|--------|
| `src/dusk/input/input.csv` | `tools/input/csv/` | input action enum + names |
| `src/dusk/display/color.csv` | `tools/color/csv/` | color constants |
| `src/dusk/rpg/item/item.csv` | `tools/item/csv/` | item enum + metadata |
| `src/dusk/rpg/story/storyflag.csv` | `tools/story/csv/` | story flag enum + initial values |
Generated headers are written to `build-<target>/generated/` and included via `target_include_directories`.
## Asset system
Assets are packed into `dusk.dsk` (a zip archive) at build time from the `assets/` directory. At runtime `asset.c` opens the archive and serves files from it.
Loading is asynchronous: `assetLock()` registers a load request; the background thread calls the appropriate loader; call `assetRequireLoaded()` to block until ready. `assetUnlock()` / `assetUnlockEntry()` releases the entry so it can be reclaimed.
Loaders are registered per type (`assetloadertype_t`) and live under `src/dusk/asset/loader/`. Platform-specific asset init (finding the .dsk file) is in `src/dusk{platform}/asset/`.
## Display subsystem
The display system is currently organized around immediate GPU-style rendering: `mesh_t` (vertex buffers), `shader_t` (GLSL on GL / TEV state on Dolphin), `texture_t`, and `framebuffer_t`. See [display-refactor.md](display-refactor.md) for the planned move to a render-queue model (needed for a future Saturn port).
The `spritebatch_t` (`display/spritebatch/`) accumulates 2D quads and flushes in batches — the primary 2D drawing primitive used by the RPG layer.
-115
View File
@@ -1,115 +0,0 @@
# Asset System
Source: `src/dusk/asset/`
All game assets are packed into `dusk.dsk` (a zip archive) at build time and served from it at runtime. The asset system manages async loading, reference counting, and platform-specific archive location.
See [architecture.md](architecture.md#asset-system) for the high-level overview.
---
## Asset archive
The archive is opened at `assetInit()`. `assetFileExists(filename)` checks for a file without loading it. The file path format inside the archive matches the layout of the `assets/` source directory.
On each platform, `assetInitPlatform()` locates the `.dsk` file (e.g. adjacent to the binary on Linux, on the SD card on PSP).
---
## Entry lifecycle
An `assetentry_t` represents one file being managed by the system. States:
```
NOT_STARTED → PENDING_ASYNC → LOADING_ASYNC → PENDING_SYNC → LOADING_SYNC → LOADED
└→ ERROR
```
- **PENDING_ASYNC / LOADING_ASYNC**: the background thread is handling I/O (file reads, decompression).
- **PENDING_SYNC / LOADING_SYNC**: the main thread needs to finish loading (e.g. uploading to GPU), triggered during `assetUpdate()`.
The async/sync split exists because GPU operations must happen on the main thread.
---
## Using assets
```c
// Acquire a loaded entry (blocks until loaded):
assetentry_t *entry = assetLock(filename, ASSET_LOADER_TYPE_TEXTURE, &input);
errorChain(assetRequireLoaded(entry));
// Use the loaded data:
texture_t *tex = &entry->data.texture.texture;
// Release when done:
assetUnlockEntry(entry);
```
`assetLock` finds-or-creates an entry and increments its reference count. `assetUnlock` / `assetUnlockEntry` decrements it; when it reaches zero the entry is reclaimed at the next `assetUpdate()`.
To subscribe to async completion instead of blocking:
```c
eventSubscribe(&entry->onLoaded, myCallback, myUser);
```
---
## Loader types
| Type constant | File | Output struct accessed via |
|---|---|---|
| `ASSET_LOADER_TYPE_TEXTURE` | `.png` etc. | `entry->data.texture.texture` |
| `ASSET_LOADER_TYPE_TILESET` | tileset descriptor | `entry->data.tileset.tileset` |
| `ASSET_LOADER_TYPE_MESH` | mesh data | `entry->data.mesh.mesh` |
| `ASSET_LOADER_TYPE_LOCALE` | `.po` file | internal to locale manager |
| `ASSET_LOADER_TYPE_JSON` | `.json` | `entry->data.json.*` |
Each loader type registers `loadAsync`, `loadSync`, and `dispose` callbacks in `ASSET_LOADER_CALLBACKS[]`.
The async callback runs on the loader thread; the sync callback runs on the main thread during `assetUpdate()`. Most loaders do file I/O async and GPU upload sync.
### Error handling inside loaders
Use these macros instead of `errorThrow` / `errorChain` inside loader callbacks — they also set the entry state to ERROR:
```c
assetLoaderErrorChain(loading, someCall());
assetLoaderErrorThrow(loading, "Descriptive message");
```
---
## Low-level file I/O (`asset/assetfile.h`)
`assetfile_t` wraps a `zip_file_t` handle and provides streaming reads:
```c
assetFileInit(&file, "textures/player.png", NULL, NULL);
assetFileOpen(&file);
assetFileRead(&file, buffer, size);
assetFileClose(&file);
assetFileDispose(&file);
// Read entire file into a malloc'd buffer:
uint8_t *buf; size_t size;
assetFileReadEntire(&file, &buf, &size); // caller frees buf
```
For line-by-line text parsing (`assetfilelinereader_t`):
```c
assetFileLineReaderInit(&reader, &file, readBuf, readBufSize, outBuf, outBufSize);
while(!reader.eof) {
errorChain(assetFileLineReaderNext(&reader));
// reader.outBuffer contains the line, reader.lineLength its length
}
```
---
## Background loader thread
`assetUpdateAsync(thread)` is the thread entry point. It calls `assetUpdate()` in a loop, sleeping briefly between iterations, until `threadShouldStop()` returns true. The main thread also calls `assetUpdate()` once per frame to process the sync phase.
Up to `ASSET_LOADING_COUNT_MAX` (4) entries can be loading concurrently.
Up to `ASSET_ENTRY_COUNT_MAX` (128) entries can exist at once.
-92
View File
@@ -1,92 +0,0 @@
# Display Refactor Progress
## Immediate Goal
Render a 32x32 white square through the new render opcode stack on Linux.
## Architecture (summary)
See `.claude/display-refactor.md` for the full design.
- `src/dusk/render/` -- opcode format + buffer + submission API (the *contract*).
- Platform backends (e.g. `src/duskgl/`) consume the buffer and translate to native API calls.
- `src/dusk/display/` -- orchestration shell only: `displayInit`, `displayUpdate`, `displayDispose`.
- Scenes call `renderSprite(...)`, `renderClear(...)`. The backend executes the intent.
## Opcode format (32 bytes)
Every command starts with a 4-byte `ropheader_t` (opcode, flags, depth). Two commands defined:
- `ROP_CLEAR` (32 bytes) -- clear with a color.
- `ROP_DRAW_SPRITE` (32 bytes) -- screen-space int16 x/y/w/h + tint color.
## Milestone 1 -- Archive + strip existing display deps ✓
- [x] Old `src/dusk/display/` archived (now deleted from working tree via git).
- [x] Old `src/duskgl/display/` removed (new GL renderer replaces it).
- [x] `engine.c` stripped to minimal subsystems, set to `SCENE_TYPE_TEST`.
- [x] `scene.c` stripped of old display/shader/screen references.
- [x] `console.c` stripped of display deps.
- [x] `ui/CMakeLists.txt` gutted (re-implementation deferred).
- [x] `asset/loader/CMakeLists.txt` -- display loaders disabled.
- [x] `asset/loader/assetloader.h` -- display loader types removed.
- [x] `rpg/overworld/chunk.h` -- mesh_t / meshvertex_t removed.
- [x] `rpg/overworld/map.c` -- mesh/spritebatch calls removed.
- [x] `scene/overworld/sceneoverworld.c` -- stubbed to empty callbacks.
- [x] Test suite display tests disabled.
## Milestone 2 -- Render opcode system ✓
- [x] `src/dusk/render/rop.h` -- `ropheader_t`, `ropclear_t`, `ropsprite_t`.
- [x] `src/dusk/render/ropbuffer.h/.c` -- `ROPBUFFER` global, reset, alloc.
- [x] `src/dusk/render/render.h/.c` -- `renderClear()`, `renderSprite()`.
- [x] `src/dusk/render/CMakeLists.txt`.
## Milestone 3 -- New minimal display shell ✓
- [x] `src/dusk/display/display.h/.c` -- init/update/dispose, calls platform hooks.
- [x] `src/dusk/display/displaystate.h` -- cull/depth/blend flags.
- [x] `src/dusk/display/color.csv` + `CMakeLists.txt` -- color generation kept.
## Milestone 4 -- GL backend ✓
- [x] `src/duskgl/render/rendergl.h/.c`:
- GL 3.3 core shader (ortho projection, solid color, no texture yet).
- `renderGLInit` -- creates VAO/VBO/shader.
- `renderGLFlush(buf, w, h)` -- walks ROPBUFFER, GL calls per opcode.
- `ROP_CLEAR``glClearColor` + `glClear`.
- `ROP_DRAW_SPRITE` → 6-vertex quad, `glDrawArrays`.
- [x] `src/duskgl/error/errorgl.h/.c` -- `errorGLCheck`.
- [x] `src/duskgl/CMakeLists.txt`.
- [x] `src/dusksdl2/display/displaysdl2.h/.c` updated:
- `displaySDL2Init` -- SDL2 window + GL 3.3 context + `renderGLInit`.
- `displaySDL2Flush(ropbuffer_t *)` -- MakeCurrent + `renderGLFlush`.
- `displaySDL2Swap` -- SDL_GL_SwapWindow.
- [x] `src/dusklinux/display/displayplatform.h` updated with new macros.
## Milestone 5 -- Test scene ✓
- [x] `SCENE_TYPE_TEST` added to `scenetype.h/.c`.
- [x] `src/dusk/scene/test/scenetest.h/.c`:
- `renderClear(color(32, 32, 48, 255))` -- dark blue-grey background.
- `renderSprite(100, 100, 32, 32, COLOR_WHITE)` -- 32x32 white square.
- [x] `engine.c` starts with `SCENE_TYPE_TEST`.
## Milestone 6 -- Verified ✓
- [x] Build succeeds with no errors (2026-06-18).
- [x] Engine initializes: SDL window + GL context + shader + test scene.
- [x] No crashes running for 5+ seconds.
- [ ] 32x32 white square visually confirmed on screen.
---
## Status: BUILD PASSING -- awaiting visual confirmation
---
## Decisions log
**2026-06-18** -- `color_t = color4b_t` (from generated `display/color.h`). The color generation pipeline (color.csv + Python tool) is kept in the new minimal `src/dusk/display/CMakeLists.txt`.
**2026-06-18** -- `ROP_SIZE = 32`. All opcodes fixed 32 bytes. 3D quads will be 64 bytes when added later.
**2026-06-18** -- Depth sort deferred. Buffer stores unsorted commands; painter platforms sort on flush. GL uses Z-buffer.
**2026-06-18** -- Texture system not yet wired into the opcode pipeline. `ROP_DRAW_SPRITE` with `texture=0` uses solid tint color only (no sampler). Texture handle system comes next.
**2026-06-18** -- GL backend uses GL 3.3 Core profile. Shader takes screen-space pixel coordinates and converts to clip space using window size queried from SDL each frame.
**2026-06-18** -- `ROPBUFFER` is a global (4096 slots × 32 bytes = 128 KB). Reset at start of each frame in `displayUpdate`.
**2026-06-18** -- `ui/`, `rpg/overworld` display code, asset display loaders all temporarily stubbed/disabled. Will be rewritten against the new render API.
-352
View File
@@ -1,352 +0,0 @@
# Display System
Source: `src/dusk/display/`
The display system is the rendering pipeline. It is abstracted across platforms via `displayplatform.h` — see [architecture.md](architecture.md) for the abstraction pattern. The current concrete backends are OpenGL (`src/duskgl/`) and GX/Dolphin (`src/duskdolphin/`).
For the planned render-queue refactor (required for Saturn), see [display-refactor.md](display-refactor.md).
---
## Render / ROP system (`display/render/`)
The ROP (Render OPcode) system is the low-level, backend-agnostic drawing API. All game drawing goes through this layer; backends (`rendergl.c`, `renderpsp.c`, `renderdolphin.c`) execute the commands at display flush time.
### API (`display/render/render.h`)
```c
/* Clear the framebuffer */
void renderClear(color_t color);
/* 2D textured quad at pixel coordinates */
void renderSprite(
int16_t x, int16_t y, int16_t w, int16_t h,
int16_t depth, /* 0=front … 32767=back */
rtexture_t texture, color_t tint
);
/* Set perspective projection for subsequent 3D draws */
void renderSetProjection(
fixed_t fovY, fixed_t aspect, fixed_t nearZ, fixed_t farZ
);
/* Set camera position/target for subsequent 3D draws */
void renderSetView(
int16_t eyeX, int16_t eyeY, int16_t eyeZ,
int16_t tgtX, int16_t tgtY, int16_t tgtZ
);
/* World-space quad: center point + right half-extent + up half-extent */
void renderQuad3D(
int16_t cx, int16_t cy, int16_t cz,
int16_t rx, int16_t ry, int16_t rz,
int16_t ux, int16_t uy, int16_t uz,
int16_t depth,
rtexture_t texture, color_t tint
);
/* Create / dispose an 8-bit indexed palette texture */
rtexture_t renderTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, /* w×h pixel indices (0-255) */
const color_t *palette /* 256 RGBA colour entries */
);
void renderTextureDispose(rtexture_t tex);
/* Mutable pointers to the texture's CPU-side data.
* Write directly to these; the next draw call picks up the changes.
* GL: dirty flag set on getter call; glTexSubImage2D at next bind.
* PSP: re-pads indices and converts palette → ABGR at bind time.
* Dolphin: re-tiles CI8 and converts palette → RGB5A3 at bind time. */
color_t *renderTextureGetPalette(rtexture_t tex); /* color_t[256] */
uint8_t *renderTextureGetIndices(rtexture_t tex); /* uint8_t[w*h] */
```
### Coordinate conventions
| Domain | Type | Scale | Notes |
|---|---|---|---|
| 3D world positions | `int16_t` | 1 unit = 1 cm | Matches PS1 GTE / N64 RSP native format |
| Camera/projection params | `fixed_t` | Q24.8 | `FIXED(x)` for literals |
| 2D screen positions | `int16_t` | pixels | Origin top-left |
| UV coords | `uint8_t` | 0255 → 0.01.0 | Stored in ROP structs |
### Palettized textures
All textures are 8-bit indexed. `renderTextureCreate` takes:
- `indices`: one `uint8_t` per pixel (0255), row-major
- `palette`: exactly **256** `color_t` RGBA entries
**Per-platform storage:**
| Platform | CPU source of truth | GPU/native format | When derived |
|---|---|---|---|
| GL (Linux/Vita) | `color_t palette[256]` + `uint8_t *cpuIndices` in slot | `GL_R8` index tex + `GL_RGBA` 256×1 palette tex | Lazy: dirty flag set by getter, `glTexSubImage2D` at next bind |
| PSP | `color_t palette[256]` + unpadded `uint8_t *cpuIndices` | Stride-padded indices (POT ≥ 8) + ABGR8888 CLUT in shared `pspAbgrBuf` | Every `bindTexture` call; dcache-flushed before GU reads |
| Dolphin/GC/Wii | `color_t palette[256]` + unpadded `uint8_t *cpuIndices` | CI8 tiled (8×4 tiles, 32 B/tile) + RGB5A3 TLUT in `tlutData` | Every `bindTexture` call; `DCFlushRange` before GX load |
**GL palette shader detail**: The fragment shader samples the R8 index texture, converts the normalised float back to an exact texel centre with `raw*(255/256) + 0.5/256`, then looks up the 256×1 palette texture. This gives pixel-exact results for all 256 index values and allows independent real-time updates to indices or palette.
**Dolphin RGB5A3 encoding**:
- Opaque (`a == 255`): bit 15 = 1, RGB555
- Transparent: bit 15 = 0, A3RGB4 (alpha quantised to 3 bits — dithered transparency is planned for a future pass)
### ROP buffer (`display/render/ropbuffer.h` / `rop.h`)
Commands are written into `ROPBUFFER` (a static byte array) then replayed by the backend at flush time. All ops are fixed-size aligned structs:
| Op | Struct | Size |
|---|---|---|
| `ROP_CLEAR` | `ropclear_t` | 32 bytes |
| `ROP_DRAW_SPRITE` | `ropsprite_t` | 32 bytes |
| `ROP_SET_PROJECTION` | `ropprojection_t` | 32 bytes |
| `ROP_SET_VIEW` | `ropview_t` | 32 bytes |
| `ROP_DRAW_QUAD_3D` | `ropquad3d_t` | 64 bytes |
| `ROP_DRAW_TILEMAP_CHUNK` | `roptilemapc_t` | 32 bytes |
`ropOpSize(op)` returns the byte size for any op. Backends iterate with `offset += ropOpSize(op)`.
### Texture handles (`display/render/rtexture.h`)
`rtexture_t` is a `uint16_t` index into the platform's texture table. `RTEXTURE_NONE` (0 or a sentinel) means "white fallback". Tables are platform-static; handles are valid until `renderTextureDispose` is called.
### Tilemap chunk handles (`display/render/rtilemapchunk.h`)
`rtilemapchunk_t` is a `uint16_t` index into the platform's chunk table. `RTILEMAPCHUNK_INVALID` (0) means no-op. Chunks are pre-built at map load time; each backend constructs its native draw structure once (VAO+VBO on GL, display list on PSP/GX/N64) and the ROP entry costs only a handle lookup + single native draw call per frame.
```c
/* Build once at map load */
rtilemapchunk_t chunk = renderTilemapChunkCreate(
chunkW, chunkH, /* size in tiles */
tileW, tileH, /* pixels per tile */
tileset, /* rtexture_t of the packed tileset */
tileIndices /* uint8_t[chunkW*chunkH], row-major tile indices */
);
/* Each frame for visible chunks */
renderTilemapChunk(screenX, screenY, depth, chunk);
/* At map unload */
renderTilemapChunkDispose(chunk);
```
Animated tiles should be drawn on top as separate `renderSprite()` calls; the chunk itself is treated as static geometry and never rebuilt at runtime.
**Per-platform build:**
| Platform | What's built at create time | Draw cost per frame |
|---|---|---|
| GL (Linux/Vita) | VAO + VBO (`GL_STATIC_DRAW`), `uOffset` uniform translates to screen pos | 1 `glDrawArrays` |
| PSP | GU display list in uncached EDRAM | 1 `sceGuCallList` |
| GC/Wii | Compiled GX display list | 1 `GX_CallDispList` |
| PS1 | Pre-linked POLY_FT4/SPRT chain | Linked into OT at one slot |
| N64 | RDP display list with pre-scheduled `LOAD_TILE` batches (TMEM-aware) | 1 `gSPDisplayList` |
| Saturn | VDP2 plane config + VRAM tilemap data | Scroll register write only |
---
## Initialization order
Within the display system, init must follow this order (enforced in `engine.c`):
```
displayInit → uiInit → uiTextboxInit
```
Within `displayInit`, the platform typically initialises: framebuffer → screen → shader list → textures → spritebatch → text system.
---
## `display_t` / `displaystate_t`
`display_t DISPLAY` is the global display instance (type alias for `displayplatform_t`).
`displaystate_t` carries per-draw-call render state flags:
```c
DISPLAY_STATE_FLAG_CULL // face culling
DISPLAY_STATE_FLAG_DEPTH_TEST // depth testing
DISPLAY_STATE_FLAG_BLEND // alpha blending
```
Set state before drawing with `displaySetState(state)`.
---
## Screen (`display/screen/`)
`screen_t SCREEN` manages the logical viewport that game content renders into. On dynamic-size platforms (Linux/SDL2) the screen can differ from the native window/framebuffer resolution.
Screen modes:
```
SCREEN_MODE_BACKBUFFER — maps 1:1 to backbuffer
SCREEN_MODE_FIXED_SIZE — fixed pixel dimensions
SCREEN_MODE_ASPECT_RATIO — fixed aspect, scale to fit
SCREEN_MODE_FIXED_HEIGHT — fixed height, width scales
SCREEN_MODE_FIXED_WIDTH — fixed width, height scales
SCREEN_MODE_FIXED_VIEWPORT_HEIGHT — fixed viewport height
```
The linux target defines `DUSK_DISPLAY_SCREEN_HEIGHT=240`, producing a 240p fixed-height viewport.
Render loop usage:
```c
screenBind(); // set up viewport, projection
// ... draw game content ...
screenUnbind();
screenRender(); // blit to backbuffer / current framebuffer
```
`SCREEN.width` / `SCREEN.height` are the logical dimensions used for world-to-screen math — always prefer these over the framebuffer dimensions.
---
## Framebuffer (`display/framebuffer/`)
`framebuffer_t FRAMEBUFFER_BACKBUFFER` is the platform backbuffer. `FRAMEBUFFER_BOUND` points to the currently-bound framebuffer (or `NULL` for backbuffer).
```c
frameBufferInitBackBuffer(); // called once at startup
frameBufferBind(fb); // NULL → backbuffer
frameBufferClear(FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, COLOR_BLACK);
frameBufferGetWidth(fb) / frameBufferGetHeight(fb) / frameBufferGetAspect(fb);
```
On platforms with `DUSK_DISPLAY_SIZE_DYNAMIC`, off-screen framebuffers can be created with `frameBufferInit(fb, w, h)` and disposed with `frameBufferDispose(fb)`. Fixed-resolution platforms (PSP, GameCube) only ever use the backbuffer.
---
## Mesh (`display/mesh/`)
`mesh_t` is a vertex buffer. The type is `meshplatform_t` (e.g. a VAO+VBO on GL, a GX display list on Dolphin).
```c
meshInit(&mesh, MESH_PRIMITIVE_TYPE_TRIANGLES, vertexCount, verticesPtr);
meshFlush(&mesh, offset, count); // upload CPU vertices → GPU
meshDraw(&mesh, offset, count); // draw; pass -1 for count to draw all
meshDispose(&mesh);
```
**Key distinction**: `meshFlush` uploads data to GPU memory; `meshDraw` issues the draw call. For static geometry (chunk meshes) you call `meshFlush` once on load, then `meshDraw` every frame. For dynamic geometry (spritebatch) you `meshFlush` + `meshDraw` each frame.
`meshvertex_t` (`display/mesh/meshvertex.h`) contains:
- `float_t uv[2]` — texture coordinates
- `float_t pos[3]` — position
- Optionally `color_t color` if `MESH_ENABLE_COLOR` is defined (off by default)
Primitive mesh generators live alongside `mesh.h`: `quad.h`, `plane.h`, `cube.h`, `sphere.h`, `capsule.h`, `triprism.h`.
---
## Shader (`display/shader/`)
`shader_t` is `shaderplatform_t` (GLSL program on GL, TEV state block on Dolphin).
```c
shaderInit(&shader, &definition);
shaderBind(&shader);
shaderSetMatrix(&shader, "uModel", modelMat);
shaderSetTexture(&shader, "uTexture", &texture);
shaderSetColor(&shader, "uColor", COLOR_WHITE);
shaderSetMaterial(&shader, &material);
shaderDispose(&shader);
```
### Shader list (`display/shader/shaderlist.h`)
The engine maintains a small set of built-in shaders in `SHADER_LIST_DEFS[]`. Currently only one is defined:
- `SHADER_LIST_SHADER_UNLIT``SHADER_UNLIT` — unlit textured/colored rendering, used for all world and entity drawing.
`shaderListInit()` compiles/uploads all built-in shaders and sets shared projection/view matrices. Call once after display init.
### Materials (`display/shader/shadermaterial.h`)
`shadermaterial_t` is a union of all shader-specific material structs. Currently only `shaderunlitmaterial_t`:
```c
shadermaterial_t mat = {
.unlit = {
.color = COLOR_WHITE,
.texture = &myTexture, // NULL for solid color
}
};
shaderSetMaterial(&SHADER_UNLIT, &mat);
```
---
## Texture (`display/texture/`)
```c
textureInit(&texture, width, height, format, data);
textureDispose(&texture);
```
Width and height **must be powers of two** (asserted at init time).
`textureformat_t` is `textureformatplatform_t`. Supported formats vary by platform; the common ones are `TEXTURE_FORMAT_RGBA` and `TEXTURE_FORMAT_PALETTE`.
`texturedata_t` is a union:
```c
// RGBA:
data.rgbaColors = colorArray;
// Paletted:
data.paletted.indices = indexArray;
data.paletted.palette = &palette; // palette color count must be power of two
```
**Built-in textures** (defined in `texture.c`, no asset loading needed):
- `TEXTURE_WHITE` — 4×4 solid white
- `TEXTURE_TEST` — 4×4 black/magenta checkerboard
### Palette (`display/texture/palette.h`)
Up to `PALETTE_COUNT` (6) global palettes in `PALETTES[]`, each holding up to `PALETTE_COLOR_COUNT` (255) `color_t` entries.
### Tileset (`display/texture/tileset.h`)
A tileset slices a texture into a grid of equal-sized tiles. Used by fonts and UI frames. The tileset does not own the texture — it references a `texture_t *`.
---
## SpriteBatch (`display/spritebatch/`)
The primary 2D/billboard drawing primitive. Accumulates `spritebatchsprite_t` quads and flushes them in batches of `SPRITEBATCH_FLUSH_COUNT` (16) sprites at a time.
```c
// Per frame:
spriteBatchClear();
spriteBatchBuffer(sprites, count, &SHADER_UNLIT, material); // auto-flushes when batch full
spriteBatchFlush(); // flush remaining
// Low-level: write directly to an external mesh (for baking static geometry):
spriteBatchBufferToMesh(sprites, count, vertices, verticesSize);
```
`spritebatchsprite_t`:
```c
typedef struct {
vec3 min, max; // 3D bounding box
vec2 uvMin, uvMax; // texture region
} spritebatchsprite_t;
```
The global `SPRITEBATCH` and its vertex backing array `SPRITEBATCH_VERTICES[]` are defined externally to the struct to satisfy alignment requirements on certain platforms.
---
## Text (`display/text/`)
Text rendering uses `FONT_DEFAULT` (loaded during `textInit()`), which references a texture and a tileset. Characters start at ASCII `!` (`TEXT_CHAR_START`).
```c
textDraw(x, y, "Hello", COLOR_WHITE, &FONT_DEFAULT);
textMeasure("Hello", &FONT_DEFAULT, &outWidth, &outHeight);
// Single-char sprite for manual layout:
spritebatchsprite_t s = textGetSprite(pos, 'A', &FONT_DEFAULT);
```
`font_t` holds a `texture_t *` and a `tileset_t *` — both are owned by the asset system, not the font struct.
-93
View File
@@ -1,93 +0,0 @@
# Input System
Source: `src/dusk/input/`
The input system decouples physical hardware buttons from logical game actions via a binding layer. Actions are defined in `src/dusk/input/input.csv` and code-generated into `inputaction_t` enum values — see [architecture.md](architecture.md#code-generation-from-csv).
---
## Concepts
**Button** (`inputbutton_t`) — a physical input source: a keyboard scancode, gamepad button, gamepad axis, or pointer axis. The available button types depend on which `DUSK_INPUT_*` defines are active for the target platform.
**Action** (`inputaction_t`) — a logical game input (e.g. `INPUT_ACTION_UP`, `INPUT_ACTION_CONFIRM`, `INPUT_ACTION_RAGEQUIT`). Each action accumulates a float value `[0.0, 1.0]` from all buttons bound to it.
**Binding** — a many-to-one mapping from buttons to actions. Bindings are registered at runtime with `inputBind(button, action)`.
---
## Querying actions
```c
// Boolean helpers (current frame):
inputIsDown(action) // value > 0 this frame
inputPressed(action) // down this frame but not last
inputReleased(action) // down last frame but not this
// Last frame state:
inputWasDown(action)
// Raw float value:
inputGetCurrentValue(action) // [0.0, 1.0]
inputGetLastValue(action)
// Axis helpers — combine two opposing actions into a signed float:
float_t h = inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT); // -1.0 to 1.0
inputAxis2D(negX, posX, negY, posY, result); // fills vec2
inputAngle2D(negX, posX, negY, posY, result); // atan2-based normalized direction
// Deadzone:
float_t clean = inputDeadzone(rawValue, 0.1f);
```
---
## Dynamic values (`DUSK_TIME_DYNAMIC`)
On platforms with variable frame rates, each action also tracks `dynamicDelta`-scaled values:
```c
inputGetCurrentValueDynamic(action)
inputGetLastValueDynamic(action)
```
These account for the actual time elapsed since the last frame, so movement calculated from them is frame-rate independent.
---
## Events on actions
Each `inputactiondata_t` exposes `onPressed` and `onReleased` events:
```c
eventSubscribe(&INPUT.actions[INPUT_ACTION_CONFIRM].onPressed, myCallback, myUser);
```
The callback signature is `void cb(void *params, void *user)`. `params` is always `NULL` for input events.
---
## Buttons and bindings
Physical buttons are typed via `inputbuttontype_t`:
| Constant | When available | Payload |
|---|---|---|
| `INPUT_BUTTON_TYPE_KEYBOARD` | `DUSK_INPUT_KEYBOARD` | `inputscancode_t` |
| `INPUT_BUTTON_TYPE_GAMEPAD` | `DUSK_INPUT_GAMEPAD` | `inputgamepadbutton_t` |
| `INPUT_BUTTON_TYPE_GAMEPAD_AXIS` | `DUSK_INPUT_GAMEPAD` | axis + positive direction flag |
| `INPUT_BUTTON_TYPE_POINTER` | `DUSK_INPUT_POINTER` | `inputpointeraxis_t` |
Button names and default bindings are defined in `input.csv`. Look up a button by name:
```c
inputbutton_t btn = inputButtonGetByName("keyboard_w");
inputBind(btn, INPUT_ACTION_UP);
```
`INPUT_BUTTON_DATA[]` holds runtime state (current/last raw values) for every physical button.
---
## Platform platform-specific reads
`inputButtonGetValuePlatform(button)` is the one required platform function — it returns the current raw `[0.0, 1.0]` value for a button. The platform implementations live in `src/dusk{platform}/input/`.
-149
View File
@@ -1,149 +0,0 @@
# Cutscenes
Two distinct layers: a low-level engine sequencer (`src/dusk/cutscene/`) and a higher-level RPG wrapper (`src/dusk/rpg/cutscene/`). Almost all game code works with the RPG layer.
---
## Engine sequencer (`src/dusk/cutscene/`)
`cutscene_t CUTSCENE` is a minimal event runner with up to `CUTSCENE_EVENT_COUNT_MAX` (16) `cutsceneevent_t` slots. Each event has three callbacks:
```c
typedef struct {
errorret_t (*onStart)(void);
errorret_t (*onUpdate)(void);
errorret_t (*onEnd)(void);
} cutsceneevent_t;
```
API:
```c
cutscenePlay(events, count); // copy events array and start from index 0
cutsceneAdvance(); // end current event, start next (deactivates after last)
cutsceneStop(); // abort immediately
cutsceneIsActive(); // bool
```
This layer is primarily used by the RPG cutscene system — game code doesn't normally touch it directly.
---
## RPG cutscene layer (`src/dusk/rpg/cutscene/`)
### Data structures
A `cutscene_t` is just a pointer to an item array and a count:
```c
typedef struct cutscene_s {
const cutsceneitem_t *items;
uint8_t itemCount;
} cutscene_t;
```
A `cutsceneitem_t` is a tagged union of all item types:
```c
typedef struct cutsceneitem_s {
cutsceneitemtype_t type;
union {
cutscenetext_t text; // display text in textbox
cutscenecallback_t callback; // call a void(*)(void) function
cutscenewait_t wait; // pause for a fixed_t duration (seconds)
const cutscene_t *cutscene; // nest another cutscene
};
} cutsceneitem_t;
```
### Item types
| Type constant | Payload | Behaviour |
|---|---|---|
| `CUTSCENE_ITEM_TYPE_TEXT` | `cutscenetext_t``text[256]` + `rpgtextboxpos_t position` | Shows textbox; advances on player confirm input |
| `CUTSCENE_ITEM_TYPE_CALLBACK` | `cutscenecallback_t` (function pointer) | Calls the function once, then immediately advances |
| `CUTSCENE_ITEM_TYPE_WAIT` | `cutscenewait_t` (a `fixed_t` in seconds) | Counts down `animTime` each frame, then advances |
| `CUTSCENE_ITEM_TYPE_CUTSCENE` | `const cutscene_t *` | Plays the nested cutscene before continuing |
### Runtime state
`cutscenesystem_t CUTSCENE_SYSTEM` tracks:
- `scene` — pointer to the active `cutscene_t`
- `currentItem` — index into `scene->items[]`
- `data` — per-item runtime data (`cutsceneitemdata_t`, currently just `cutscenewaitdata_t`)
- `mode` — the current `cutscenemode_t`
API:
```c
cutsceneSystemStartCutscene(cutscene); // begin playing a cutscene
cutsceneSystemNext(); // advance to next item
cutsceneSystemUpdate(); // called each frame from rpgUpdate
cutsceneSystemGetCurrentItem(); // inspect active item
```
### Cutscene mode (`cutscenemode.h`)
Each item can run in one of three modes:
```c
CUTSCENE_MODE_NONE // no cutscene active
CUTSCENE_MODE_FULL_FREEZE // pause everything (not yet used)
CUTSCENE_MODE_INPUT_FREEZE // player input blocked (default: CUTSCENE_MODE_INITIAL)
CUTSCENE_MODE_GAMEPLAY // player can still move during cutscene
```
`cutsceneModeIsInputAllowed()` is checked by `entityUpdate()` before invoking the movement callback — the player cannot walk when in INPUT_FREEZE mode.
### Defining a cutscene
Cutscenes are defined as `static const` arrays in header files under `rpg/cutscene/scene/`. Example (`testcutscene.h`):
```c
static const cutsceneitem_t MY_CUTSCENE_ITEMS[] = {
{
.type = CUTSCENE_ITEM_TYPE_TEXT,
.text = { .text = "Hello!", .position = RPG_TEXTBOX_POS_BOTTOM }
},
{
.type = CUTSCENE_ITEM_TYPE_WAIT,
.wait = FIXED(1.5f)
},
{
.type = CUTSCENE_ITEM_TYPE_CUTSCENE,
.cutscene = &ANOTHER_CUTSCENE
},
};
static const cutscene_t MY_CUTSCENE = {
.items = MY_CUTSCENE_ITEMS,
.itemCount = sizeof(MY_CUTSCENE_ITEMS) / sizeof(cutsceneitem_t)
};
```
Attach to an NPC via its interact component:
```c
entity->interact.type = ENTITY_INTERACT_CUTSCENE;
entity->interact.data.cutscene = &MY_CUTSCENE;
```
---
## Textbox (`src/dusk/rpg/rpgtextbox.h`)
`rpgtextbox_t RPG_TEXTBOX` is the global textbox state:
```c
typedef struct {
rpgtextboxpos_t position; // RPG_TEXTBOX_POS_TOP or RPG_TEXTBOX_POS_BOTTOM
bool_t visible;
char_t text[RPG_TEXTBOX_MAX_CHARS]; // 256 chars
} rpgtextbox_t;
```
API:
```c
rpgTextboxShow(position, text); // copies text, sets visible = true
rpgTextboxHide(); // sets visible = false
rpgTextboxIsVisible(); // bool
```
The textbox state is read by `ui/uitextbox.c` during the UI render pass to draw the dialogue box on screen. `rpgtextbox.c` itself does no rendering.
-140
View File
@@ -1,140 +0,0 @@
# Entities
Source: `src/dusk/rpg/entity/`
---
## Storage
Entities live in a single fixed global array:
```c
entity_t ENTITIES[ENTITY_COUNT]; // ENTITY_COUNT = 64
```
A slot is "empty" when `entity->type == ENTITY_TYPE_NULL`. Never allocate entity memory dynamically — always find a free slot with `entityGetAvailable()`, which returns its index (`0xFF` if none free).
---
## `entity_t` structure
```c
typedef struct entity_s {
uint8_t id; // index in ENTITIES[]
entitytype_t type; // ENTITY_TYPE_NULL / PLAYER / NPC
entitytypedata_t data; // union: player_t | npc_t
entitydir_t direction; // facing direction (N/S/E/W)
fixed_t position[3]; // current sub-tile position (x, y, z)
fixed_t lastPosition[3]; // position before last move (for interpolation)
entityanim_t animation; // IDLE / TURN / WALK
fixed_t animTime; // countdown timer for current animation
entityinteract_t interact; // optional interact component
} entity_t;
```
---
## Type system
Entity types are defined in `entitytype.h` using the enum+integer-typedef pattern:
```c
typedef enum { ENTITY_TYPE_NULL, ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_COUNT } entitytype_enum_t;
typedef uint8_t entitytype_t;
```
Each type has a `entitycallback_t` entry in the `ENTITY_CALLBACKS[ENTITY_TYPE_COUNT]` static table:
```c
typedef struct {
void (*init)(entity_t *entity);
void (*movement)(entity_t *entity);
bool_t (*interact)(entity_t *player, entity_t *entity);
} entitycallback_t;
```
Callbacks not applicable to a type are `NULL`; `entityUpdate()` guards against this before calling.
Type-specific data sits in `entitytypedata_t` (a union of `player_t` and `npc_t`). Currently both are stubs (`void *nothing`).
---
## Direction (`entitydir.h`)
```c
ENTITY_DIR_NORTH / EAST / SOUTH / WEST
```
Aliases: `UP = NORTH`, `DOWN = SOUTH`, `LEFT = WEST`, `RIGHT = EAST`.
Utilities:
- `entityDirGetOpposite(dir)` — returns the opposite direction.
- `entityDirGetRelative(dir, &relX, &relY)` — fills in the ±1 XY delta for that direction.
- `assertValidEntityDir(dir, msg)` — assertion macro.
---
## Animation (`entityanim.h`)
```c
ENTITY_ANIM_IDLE // standing still
ENTITY_ANIM_TURN // turning to a new direction (ENTITY_ANIM_TURN_DURATION = FIXED(0.06))
ENTITY_ANIM_WALK // mid-step (ENTITY_ANIM_WALK_DURATION = FIXED(0.1))
```
`entityAnimUpdate(entity)` decrements `animTime` each frame and transitions back to `IDLE` when it reaches zero.
`entityCanWalk(entity)` / `entityCanTurn(entity)` both return true only when `animation == ENTITY_ANIM_IDLE`.
The renderer interpolates between `lastPosition` and `position` using `animTime / WALK_DURATION` to produce smooth motion even at low frame rates.
---
## Movement
`entityWalk(entity, direction)`:
1. Converts `entity->position` to a `worldpos_t` (truncates fractional part).
2. Applies the directional delta to get `newPos`.
3. Checks the current and target tiles for ramp raise/fall logic (see [world.md](world.md)).
4. Checks `ENTITIES[]` for another entity occupying `newPos` — blocks if found.
5. On success: copies `position` to `lastPosition`, updates `position` to `newPos` (via `worldPosToFixed`), sets `animation = ENTITY_ANIM_WALK`.
`entityTurn(entity, direction)`: sets `direction` and starts a brief turn animation.
---
## Interaction (`entityinteract.h`)
The `entityinteract_t` component is embedded in every entity. It is optional — set `type = ENTITY_INTERACT_NULL` for non-interactable entities.
```c
typedef enum {
ENTITY_INTERACT_NULL,
ENTITY_INTERACT_CUTSCENE, // plays a cutscene_t *
ENTITY_INTERACT_PRINT, // prints a short message[32]
} entityinteracttype_t;
```
`entityInteractWith(player, target)` dispatches:
1. If the interact component's `type != NULL`, handles it (starts the cutscene or prints the message).
2. Otherwise falls back to `ENTITY_CALLBACKS[type].interact` if set.
---
## Player (`player.h` / `player.c`)
`playerInit()` is called via `ENTITY_CALLBACKS[ENTITY_TYPE_PLAYER].init`.
`playerInput(entity)` is the movement callback. It reads `PLAYER_INPUT_DIR_MAP[]` — a static table mapping input actions (`INPUT_ACTION_UP/DOWN/LEFT/RIGHT`) to entity directions — and calls `entityWalk` or `entityTurn` accordingly.
The player entity is normally `ENTITIES[0]` but there is no hardcoded assumption about its index beyond being initialized with `ENTITY_TYPE_PLAYER`.
---
## NPC (`npc.h` / `npc.c`)
`npcInit()`, `npcMovement()`, and `npcInteract()` provide the NPC type callbacks. Currently stubs; movement does nothing, interact returns false.
-18
View File
@@ -1,18 +0,0 @@
# RPG Layer
The RPG layer lives in `src/dusk/rpg/` and is the game-logic tier above the engine. It is initialized and ticked by `engine.c` via `rpgInit` / `rpgUpdate` / `rpgDispose`. The `rpg_t` struct is currently a stub; all meaningful state lives in the subsystems below.
## Contents
- [world.md](world.md) — scene manager, overworld map, chunks, tiles, coordinate system, camera
- [entity.md](entity.md) — entity pool, types, direction, animation, interaction, player, NPC
- [cutscene.md](cutscene.md) — cutscene system, item types, mode control, textbox
- [story.md](story.md) — story flags, items, inventory, backpack, save system
## Scene system
The scene manager (`src/dusk/scene/`) sits above the RPG layer and owns the single active scene. Scenes are identified by `scenetype_t` and registered in `SCENE_TYPES[]` (`scene/scenetype.c`) as `scenecallbacks_t` (init / update / render / dispose).
`scenedata_t` is a union so all scene structs share memory. `sceneSet(type)` defers the transition — the old scene disposes before the new one inits.
Currently the only scene is `SCENE_TYPE_OVERWORLD``src/dusk/scene/overworld/sceneoverworld.c`.
-124
View File
@@ -1,124 +0,0 @@
# Story, Items & Save
---
## Story flags (`src/dusk/rpg/story/`)
Story flags are the primary mechanism for tracking game-world state (quest progress, one-time events, unlocks). Each flag is a `uint8_t` value (`storyflagvalue_t`), so they can hold booleans or small counts.
### Defining flags
Flags are defined in `src/dusk/rpg/story/storyflag.csv`:
```
id,description,initial
test,"Test flag for debugging purposes",1
```
The build tool generates:
- A `storyflag_t` enum (e.g. `STORY_FLAG_TEST`) in the generated header.
- `STORY_FLAG_VALUES[]` — the runtime array, pre-populated with the `initial` column values.
To add a flag: add a row to the CSV. The build re-runs the Python tool automatically on the next CMake build.
### Access
```c
storyflagvalue_t v = storyFlagGet(STORY_FLAG_TEST); // macro: array read
storyFlagSet(STORY_FLAG_TEST, 1); // function: also marks save dirty
```
`storyFlagGet` is a macro that directly indexes `STORY_FLAG_VALUES[]` — no function call overhead.
---
## Items (`src/dusk/rpg/item/`)
### Item definitions
Items are defined in `src/dusk/rpg/item/item.csv`. The build tool generates `itemid_t` enum values and item metadata. `itemid_t` is a generated `uint8_t` typedef.
### Inventory (`inventory.h`)
`inventory_t` is a generic container backed by a caller-supplied `inventorystack_t` array:
```c
typedef struct {
itemid_t item;
uint8_t quantity; // max ITEM_STACK_QUANTITY_MAX (255)
} inventorystack_t;
typedef struct {
inventorystack_t *storage;
uint8_t storageSize;
} inventory_t;
```
Key operations:
```c
inventoryInit(&inv, storageArray, size);
inventoryAdd(&inv, ITEM_POTION, 3);
inventoryRemove(&inv, ITEM_POTION);
inventorySet(&inv, ITEM_POTION, 10);
inventoryGetCount(&inv, ITEM_POTION); // returns 0 if not present
inventoryItemExists(&inv, ITEM_POTION);
inventoryIsFull(&inv);
inventorySort(&inv, INVENTORY_SORT_BY_ID, false);
```
`inventory_t` itself holds no data — the backing array is always external. This avoids fixed-size struct limits and lets different inventories (backpack, shop, chest) share the same logic.
### Backpack (`backpack.h`)
The player's inventory is the global `BACKPACK` instance:
```c
extern inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX]; // 20 slots
extern inventory_t BACKPACK;
backpackInit(); // wires BACKPACK_STORAGE into BACKPACK
```
---
## Save system (`src/dusk/save/`)
The save system is stubbed out — it exists and compiles but is commented out of engine init (`engine.c`). What follows describes the design as implemented.
### Slots
`save_t SAVE` holds `SAVE_FILE_COUNT_MAX` slots:
```c
typedef struct {
savefile_t files[SAVE_FILE_COUNT_MAX];
saveplatform_t platform; // platform-specific state (paths, card handles)
} save_t;
```
### Streams
`savestream_t` (`save/savestream.h`) is a raw byte cursor used to serialize/deserialize `savefile_t`. Platform backends in `src/dusk{platform}/save/` implement the actual I/O:
- Linux: filesystem files in a save directory.
- GameCube/Wii: memory card via libogc.
### API
```c
saveInit();
saveLoad(slot); // reads platform storage → savefile_t
saveWrite(slot); // writes savefile_t → platform storage
saveDelete(slot);
saveExists(slot); // bool
saveGet(slot); // returns savefile_t *
saveDispose();
```
---
## Locale / i18n (`src/dusk/locale/`)
Translations are loaded from `.po` files in `assets/locale/` (e.g. `en_US.po`). `localemanager.c` manages the active locale and exposes a key→string lookup. `assetlocaleloader.c` parses the PO format via the asset system.
All player-visible strings must go through the locale system rather than being hardcoded. The locale is loaded asynchronously via the asset system so it is available before the first scene renders.
-114
View File
@@ -1,114 +0,0 @@
# World
Source: `src/dusk/rpg/overworld/`
---
## Coordinate system
Three nested coordinate spaces, each defined in `worldpos.h`:
| Type | Description | Unit |
|---|---|---|
| `worldpos_t` | Tile-level absolute position `{x, y, z}` | `worldunit_t` (int16) |
| `chunkpos_t` | Chunk-grid position `{x, y, z}` | `chunkunit_t` (int16) |
| `fixed_t[3]` | Smooth sub-tile position used by entities | Q24.8 fixed-point |
One chunk = `CHUNK_WIDTH × CHUNK_HEIGHT × CHUNK_DEPTH` tiles (16 × 16 × 8).
The loaded world window = `MAP_CHUNK_WIDTH × MAP_CHUNK_HEIGHT × MAP_CHUNK_DEPTH` chunks (5 × 5 × 3).
Conversion helpers (all in `worldpos.c`):
```c
worldPosToChunkPos(&worldPos, &chunkPos); // tile → chunk grid
chunkPosToWorldPos(&chunkPos, &worldPos); // chunk grid → tile origin
worldPosToChunkTileIndex(&worldPos); // tile → index within its chunk
chunkPosToIndex(&chunkPos); // chunk grid → linear index in MAP.chunks[]
worldPosToFixed(&worldPos, fixedOut); // tile → entity fixed position
fixedToWorldPos(fixedPos); // entity fixed → tile (truncates frac)
```
---
## Tiles
Defined in `tile.h` as a plain `tile_t` enum:
```
TILE_SHAPE_NULL — empty / unloaded
TILE_SHAPE_GROUND — solid flat tile
TILE_SHAPE_RAMP_* — directional ramps (N/S/E/W + diagonals NE/NW/SE/SW)
```
Key predicates:
- `tileIsWalkable(tile)` — true for GROUND and all ramp shapes.
- `tileIsRamp(tile)` — true only for ramp shapes.
Entity walk code (`entity.c`) checks both the current tile and the target tile to decide whether the entity steps forward flat, raises one Z level (walking up a ramp), or falls one Z level (stepping onto a downward ramp from above).
---
## Chunks
`chunk_t` (`chunk.h`) holds:
- `position` — its `chunkpos_t` in the world grid
- `tiles[CHUNK_TILE_COUNT]` — flat array of `tile_t`, indexed by `chunkGetTileIndex()`
- `vertices[CHUNK_VERTEX_COUNT]` / `mesh` — pre-baked mesh uploaded to GPU on load
- `entities[CHUNK_ENTITY_COUNT_MAX]` — indices into `ENTITIES[]` currently in this chunk (sentinel `0xFF`)
- `testColor` — temporary debug color (checkerboard), will be replaced by real tileset data
Tile layout within a chunk is `z * W*H + y * W + x` (Z-major, row-major in XY).
---
## Map
`map_t MAP` (`map.h`) is the single global map instance.
```c
chunk_t chunks[MAP_CHUNK_COUNT]; // flat storage — index is NOT world position
chunk_t *chunkOrder[MAP_CHUNK_COUNT]; // draw-order sorted pointers into chunks[]
chunkpos_t chunkPosition; // world-grid origin of the loaded window
bool_t loaded;
```
### Load / unload
`mapInit()` allocates chunk meshes and performs the initial load of all chunks in the starting window.
`mapPositionSet(newPos)` shifts the window:
1. Determines which of the `MAP_CHUNK_COUNT` slots remain within the new window vs. fall outside it.
2. Calls `mapChunkUnload()` on every chunk that falls outside (nulls its entity slots, zeroes `vertCount`).
3. Reuses freed slots for newly-in-range chunks; calls `mapChunkLoad()` on each.
4. Rebuilds `chunkOrder[]` in XYZ order for the new position.
### Chunk load (current stub)
`mapChunkLoad()` currently:
- Fills all tiles with `TILE_SHAPE_GROUND`
- Assigns a checkerboard debug color based on chunk XY parity
- Bakes a flat sprite-batch quad mesh for the z=0 layer and uploads it via `meshFlush()`
- Skips mesh generation for z > 0 chunks (they're empty)
### Tile lookup
```c
tile_t mapGetTile(const worldpos_t position);
```
Converts `position` to its chunk, looks up the chunk in `chunkOrder`, then indexes into `chunk->tiles[]`. Returns `TILE_SHAPE_NULL` for any out-of-bounds position or when the map is not loaded.
---
## Camera
`rpgcamera_t RPG_CAMERA` (`rpgcamera.h`) has two modes:
```c
RPG_CAMERA_MODE_FREE // free worldpos; camera.free holds the position
RPG_CAMERA_MODE_FOLLOW_ENTITY // tracks ENTITIES[followEntityId]
```
`rpgCameraGetPosition()` returns the active world tile position in either mode.
The scene renderer (`sceneoverworld.c`) uses `rpgCameraGetPosition()` to build the `glm_lookat` view matrix. When following an entity, it sub-tile interpolates between `entity->lastPosition` and `entity->position` using `entity->animTime / ENTITY_ANIM_WALK_DURATION` to smooth movement.
-96
View File
@@ -1,96 +0,0 @@
# Save & Locale
---
## Save system (`src/dusk/save/`)
Slot-based persistent storage. Currently disabled in engine init (commented out in `engine.c`) — the system is fully implemented but not yet wired up.
### Slots
Up to `SAVE_FILE_COUNT_MAX` (3) save slots. The global `SAVE` holds all slots:
```c
saveInit();
saveExists(slot); // bool_t — check before load
saveLoad(slot); // read from platform storage → SAVE.files[slot]
saveWrite(slot); // write SAVE.files[slot] → platform storage
saveDelete(slot);
savefile_t *f = saveGet(slot);
saveDispose();
```
### Save file format
`savefile_t` is the serialized struct stored per slot. Currently minimal:
```c
typedef struct {
char_t header[3]; // "DSK"
uint32_t version; // SAVE_FILE_VERSION = 1
bool_t exists;
} savefile_t;
```
Extend this struct to add game-specific save data (player position, story flags, etc.).
### Stream serialization (`save/savestream.h`)
`savestream_t` is a cursor used to read/write a save slot's bytes. It CRC32-checksums all data written through it and verifies the checksum on read.
Write a save:
```c
savestream_t stream;
// (platform opens stream for slot)
saveFileWriteHeader(&stream, SAVE_FILE_HEADER);
saveFileWriteVersion(&stream, SAVE_FILE_VERSION);
saveFileWriteBool(&stream, myFlag);
saveFileWriteInt32(&stream, myInt);
saveFileWriteString(&stream, myString, sizeof(myString));
saveStreamFinalizeWriteImpl(&stream); // writes CRC
```
Read a save:
```c
saveFileReadHeader(&stream, headerBuf);
saveFileReadVersion(&stream, &version);
saveFileReadBool(&stream, &myFlag);
saveFileReadInt32(&stream, &myInt);
saveFileReadString(&stream, myString, sizeof(myString));
saveStreamVerifyChecksumImpl(&stream, slot); // returns error if CRC mismatch
```
All multi-byte values are stored in little-endian byte order. The `saveFile*` macros are thin wrappers over the `*Impl` functions that integrate `errorChain` — always use the macros.
### Platform backends
Each `src/dusk{platform}/save/` provides `saveplatform_t` (e.g. a file path on Linux, a memory-card handle on GameCube). The stream implementations (`savestream{platform}.c`) do the actual I/O.
---
## Locale / i18n (`src/dusk/locale/`)
Translations are stored as GNU `.po` files in `assets/locale/`. Only `en_US.po` currently exists.
### Loading
`localemanager_t LOCALE` tracks the active locale and its in-progress asset entry:
```c
localeManagerInit(); // loads en_US by default
localeManagerSetLocale(&LOCALE_EN_US); // switch locale (async load)
localeManagerDispose();
```
`LOCALE_EN_US` is a predefined `localeinfo_t` constant (`name = "en-US"`, `file = "locale/en_US.po"`).
### Looking up strings
```c
char_t buf[128];
localeManagerGetText("my.key", buf, sizeof(buf), 1, /* format args */ );
```
The macro handles plural forms and `printf`-style format arguments. Pass plural `1` for singular, any other value for plural.
`assetlocaleloader.c` parses the `.po` format (msgid / msgstr pairs) into a key→string table during the async asset load phase.
-403
View File
@@ -1,403 +0,0 @@
# Coding Style
All source is C11. Everything below is derived from the existing codebase — match it exactly.
---
## File structure
### Headers (`.h`)
```c
/**
* 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`)
```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)).
---
## 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:
```c
#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 `{`:
```c
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:
```c
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 `(`:
```c
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:
```c
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
```c
typedef struct {
uint8_t id;
entitytype_t type;
} entity_t;
```
Use a named tag (`struct entity_s`) only when forward declaration is required:
```c
typedef struct entity_s {
// ...
} entity_t;
```
### Unions
```c
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:
```c
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:
```c
#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:
```c
#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:
```c
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:
```c
errorret_t engineUpdate(void);
errorret_t spriteBatchFlush(void);
```
---
## Unused parameters
Do **not** use `(void)param;` casts to suppress unused-parameter warnings. They are
redundant noise. If a callback signature is fixed by a function-pointer type and the
parameter is genuinely unused, just leave it — do not suppress:
```c
// correct — parameter unused, no cast
errorret_t sceneTestUpdate(scenedata_t *data) {
errorOk();
}
// wrong
errorret_t sceneTestUpdate(scenedata_t *data) {
(void)data;
errorOk();
}
```
---
## Global subsystem state
Each subsystem exposes a single global instance declared `extern` in the header and defined (once) in the `.c` file:
```c
// 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:
```c
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:
```c
// 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:
```c
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:
```c
static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
[ENTITY_TYPE_NULL] = { NULL },
[ENTITY_TYPE_PLAYER] = {
.init = playerInit,
.movement = playerInput
},
};
```
```c
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:
```c
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.
```c
// Walking up a ramp — only the direction the ramp faces is valid.
if(tileIsRamp(tileCurrent) && ...) {
```
Section labels inside long functions are acceptable:
```c
// Chunks
{
...
}
// Entities
{
...
}
```
Doc comments on public functions use Javadoc style with `@param` / `@return`:
```c
/**
* 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`:
```c
#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.
-177
View File
@@ -1,177 +0,0 @@
# Engine Systems
Smaller systems that support the engine but don't warrant their own file each.
---
## Time (`src/dusk/time/`)
`dusktime_t TIME` tracks fixed and dynamic delta time.
```c
TIME.delta // fixed_t: always DUSK_TIME_STEP (16ms default) on fixed-rate platforms
TIME.time // fixed_t: total elapsed time in seconds
```
On platforms with `DUSK_TIME_DYNAMIC` (Linux/SDL2):
```c
TIME.dynamicDelta // fixed_t: actual time since last frame
TIME.dynamicTime // fixed_t: total elapsed (dynamic)
TIME.dynamicUpdate // bool_t: true when a real tick occurred
```
Call `timeUpdate()` once per frame (before input/logic). `timeGetEpoch()` returns the current wall-clock time as a `dusktimeepoch_t`.
### Epoch time (`time/timeepoch.h`)
`dusktimeepoch_t` stores a Unix timestamp (double) with timezone offset. Utilities:
```c
dusktimeepoch_t e = timeGetEpoch();
timeEpochGetHours(e) // 023
timeEpochGetMinutes(e) // 059
timeEpochGetSeconds(e) // 059
timeEpochGetDayOfMonth(e) // 030
timeEpochGetMonth(e) // 011
timeEpochGetYear(e)
// Format: %Y year, %m month, %d day, %H hour, %M minute, %S second
timeEpochFormat(e, "%Y-%m-%d %H:%M:%S", buf, sizeof(buf));
// Compare: returns -1, 0, 1
timeEpochCompare(a, b);
// Timezone shift:
dusktimeepoch_t local = timeEpochSwitchTimeZone(e, offsetHours);
```
---
## Thread (`src/dusk/thread/`)
Currently only the pthread backend (`DUSK_THREAD_PTHREAD`) exists. Thread objects are `thread_t` — used primarily by the asset loader.
```c
threadInit(&thread, myCallback); // myCallback: void (*)(thread_t *)
threadStart(&thread); // blocks until thread is RUNNING
threadStop(&thread); // requests stop, blocks until STOPPED
// Inside the thread callback:
while(!threadShouldStop(&thread)) { /* work */ }
```
State flow: `STOPPED → STARTING → RUNNING → (STOP_REQUESTED) → STOPPED`.
### Mutex (`thread/threadmutex.h`)
```c
threadMutexInit(&lock);
threadMutexLock(&lock);
threadMutexUnlock(&lock);
threadMutexTryLock(&lock); // non-blocking; returns false if already held
threadMutexWaitLock(&lock); // release lock and sleep until signalled
threadMutexSignal(&lock); // wake a thread waiting on this mutex
threadMutexDispose(&lock);
```
### Thread-local storage
`THREAD_LOCAL` expands to `__thread` (GCC) on pthread platforms. Used for the per-thread error state (`ERROR_STATE` in `error/error.h`).
---
## Event (`src/dusk/event/`)
A fixed-capacity multicast callback list.
```c
// Declare backing arrays (choose a size):
eventcallback_t cbs[4];
void *users[4];
event_t myEvent;
eventInit(&myEvent, cbs, users, 4);
eventSubscribe(&myEvent, myCallback, myUser);
eventUnsubscribe(&myEvent, myCallback);
eventInvoke(&myEvent, params); // calls all subscribers; params passed as-is
```
Callback signature: `void cb(void *params, void *user)`.
`event_t` does not own its callback/user arrays. Always declare them alongside the event in the same struct or as static arrays.
---
## Console (`src/dusk/console/`)
`CONSOLE` is a scrolling in-game terminal for debug output. On POSIX platforms (`DUSK_CONSOLE_POSIX`) it also polls stdin in a thread so commands can be typed during a running session.
```c
consolePrint("Value is %d", x); // printf-style; thread-safe
consoleDraw(); // renders visible history lines to screen
```
Configuration constants (`consoledefs.h`):
- `CONSOLE_LINE_MAX` — 512 chars per line
- `CONSOLE_HISTORY_MAX` — 16 lines of scrollback
- `CONSOLE_EXEC_BUFFER_MAX` — 32 pending exec commands
`CONSOLE.visible` controls whether `consoleDraw()` actually renders anything.
---
## Log (`src/dusk/log/`)
Two simple output functions, implemented per-platform:
```c
logDebug("format %s", arg); // debug output (stdout on Linux, debug channel on consoles)
logError("format %s", arg); // error output; may pause execution on some platforms
```
These go directly to the platform's native output and are not buffered by the console history.
---
## System / Platform (`src/dusk/system/`)
`systemInit()` runs platform-specific startup (e.g. Wii PAD init, PSP kernel setup). Must be the first call in `engineInit()`.
```c
systemplatform_t p = systemGetPlatform(); // SYSTEM_PLATFORM_LINUX, _PSP, etc.
systemdialogtype_t d = systemGetActiveDialogType();
// SYSTEM_DIALOG_TYPE_NONE / RENDER_BLOCKING / TICK_BLOCKING
```
The full platform list is defined via X-macro in `system/systemplatformlist.h`:
| Constant | Value |
|---|---|
| `SYSTEM_PLATFORM_LINUX` | 0 |
| `SYSTEM_PLATFORM_KNULLI` | 1 |
| `SYSTEM_PLATFORM_PSP` | 2 |
| `SYSTEM_PLATFORM_GAMECUBE` | 3 |
| `SYSTEM_PLATFORM_WII` | 4 |
---
## Network (`src/dusk/network/`)
`network_t NETWORK` manages platform connection state. The engine calls `networkInit` / `networkUpdate` / `networkDispose`; game code uses the request API:
```c
networkRequestConnection(onConnected, onFailed, onDisconnect, user);
networkRequestDisconnection(onComplete, user);
networkIsConnected(); // bool_t
```
State machine: `DISCONNECTED → CONNECTING → CONNECTED → DISCONNECTING → DISCONNECTED`.
Network address info (after connection):
```c
networkinfo_t info = networkGetInfo();
// info.type = NETWORK_TYPE_IPV4 / IPV6
// info.ipv4.ip[4] or info.ipv6.ip[16]
```
Platform backends implement `networkPlatformInit/Update/Dispose/IsConnected`. Currently only Linux (socket-based) and PSP/Vita are implemented.
-123
View File
@@ -1,123 +0,0 @@
# UI System
Source: `src/dusk/ui/`
The UI system is an immediate-mode layer drawn on top of the game scene each frame. Elements register themselves; `uiUpdate()` ticks all elements and `uiRender()` draws them. All coordinates are in screen pixels.
---
## Lifecycle
```
uiInit() → uiTextboxInit() // init order matters — textbox depends on display being ready
uiUpdate() // each frame, before rendering
uiRender() // each frame, after scene render
uiDispose()
uiTextboxDispose()
```
---
## Element registration (`ui/uielement.h`)
Elements are stored in `UI_ELEMENTS[]`. Each has a type and a `draw` callback:
```c
typedef struct {
uielementtype_t type;
errorret_t (*draw)();
} uielement_t;
```
Currently `UI_ELEMENT_TYPE_NATIVE` elements call their `draw` function directly. New debug/HUD elements are registered by adding to this array.
---
## Textbox (`ui/uitextbox.h`)
`UI_TEXTBOX` is the global dialogue box. It word-wraps text, paginates it, and plays a typewriter scroll effect.
```c
uiTextboxSetText("Long dialogue string..."); // wraps to charsPerLine, paginates
uiTextboxUpdate(); // each frame: advance scroll, check input
uiTextboxDraw(); // draw box + text
// Pagination:
uiTextboxPageIsComplete() // true when all chars of current page are visible
uiTextboxHasNextPage()
uiTextboxNextPage()
// Subscibe to page events:
eventSubscribe(&UI_TEXTBOX.onPageComplete, cb, user);
eventSubscribe(&UI_TEXTBOX.onLastPage, cb, user);
```
`UI_TEXTBOX.advanceAction` defaults to the input action that advances dialogue — set it before calling `uiTextboxInit()` if the default doesn't suit.
Layout constants:
- `UI_TEXTBOX_TEXT_MAX` — 1024 chars
- `UI_TEXTBOX_LINES_MAX` — 64 lines
- `UI_TEXTBOX_LINES_PER_PAGE_MAX` — 3 lines visible at once
- `UI_TEXTBOX_SCROLL_CHARS_PER_TICK` — 1 char per tick (typewriter speed)
The textbox uses `UI_TEXTBOX.frame` (a `uiframe_t`) for its border rendering and `UI_TEXTBOX.font` for text.
---
## UI Frame (`ui/uiframe.h`)
9-slice bordered box rendered with a tileset:
```c
uiFrameInit(&frame);
uiFrameDraw(&frame, x, y, width, height);
uiFrameDispose(&frame);
```
The tileset is loaded from the asset system during `uiFrameInit()`. The 9 slices are arranged in the tileset grid as: top-left corner, top edge, top-right corner, left edge, fill, right edge, bottom-left, bottom edge, bottom-right.
---
## Loading overlay (`ui/uiloading.h`)
`UI_LOADING` is a full-screen loading indicator with fade-in/fade-out transitions:
```c
uiLoadingShow(onShownCallback, user); // fade in; calls callback when fully opaque
uiLoadingHide(onHiddenCallback, user); // fade out; calls callback when fully transparent
uiLoadingUpdate(delta); // each frame
uiLoadingDraw(); // each frame, over everything
```
`UI_LOADING_FADE_DURATION` is `FIXED(0.5f)` seconds. Subscribe to `UI_LOADING.onTransitionEnd` for completion events.
---
## Full-box overlay (`ui/uifullbox.h`)
Two global full-screen color overlays: `UI_FULLBOX_UNDER` (drawn before game content) and `UI_FULLBOX_OVER` (drawn after). Used for scene transitions (fade to black, etc.):
```c
uiFullboxTransition(
&UI_FULLBOX_OVER,
COLOR_TRANSPARENT, COLOR_BLACK,
FIXED(0.5f),
EASING_IN_OUT_CUBIC
);
eventSubscribe(&UI_FULLBOX_OVER.onTransitionEnd, myCallback, NULL);
uiFullboxUnderDraw(); // draw under layer
uiFullboxOverDraw(); // draw over layer
```
---
## FPS counter (`ui/uifps.h`)
`UIFPS` tracks a rolling average FPS. `uiFPSDraw()` renders it in the corner. Currently drawn as part of the debug HUD (not wired to an element slot).
---
## Player position HUD (`ui/uiplayerpos.h`)
`uiplayerpos.c` draws the player's current world tile coordinates. Debug overlay, currently drawn directly in the scene render.
-162
View File
@@ -1,162 +0,0 @@
# Utilities
Source: `src/dusk/util/`
---
## String (`util/string.h`)
**Always use these instead of stdlib equivalents** (`strcmp`, `strcpy`, `sprintf`, etc.).
```c
stringCopy(dest, src, destSize); // safe strncpy; always null-terminates
stringCompare(a, b); // -1 / 0 / 1
stringEquals(a, b); // bool_t
stringCompareInsensitive(a, b); // case-insensitive -1 / 0 / 1
stringTrim(str); // in-place strip leading/trailing whitespace
stringFindLastChar(str, c); // last occurrence pointer or NULL
stringFormat(dest, destSize, fmt, ...); // snprintf wrapper; pass NULL dest to get length
stringFormatVA(dest, destSize, fmt, args); // va_list version
stringIsWhitespace(c); // bool_t
// Parse:
stringToI32(str, &out) bool_t
stringToI64(str, &out) bool_t
stringToI16(str, &out) bool_t
stringToU16(str, &out) bool_t
stringToF32(str, &out) bool_t
// Suffix checks:
stringEndsWith(str, suffix) bool_t
stringEndsWithCaseInsensitive(str, suffix) bool_t
stringIncludesString(haystack, needle) bool_t
```
---
## Memory (`util/memory.h`)
**Always use these instead of `malloc`/`free`/`memcpy`/`memset` directly.**
```c
memoryAllocate(size) void * // malloc + tracks pointer count
memoryAlign(alignment, size) void * // aligned malloc
memoryFree(ptr) // free + decrements count
memoryReallocate(&ptr, size) // realloc
memoryResize(&ptr, oldSize, newSize) // realloc + copy (safe reshape)
memoryTrack(ptr) // track externally-malloc'd pointer
memoryCopy(dest, src, size) // memcpy
memoryMove(dest, src, size) // memmove
memorySet(dest, value, size) // memset
memoryZero(dest, size) // memset 0
memoryCompare(a, b, size) int_t // memcmp
// Useful for uploading vertex data with different source/dest layouts:
memoryCopyInterleaved(dest, destStride, src, srcStride, elementSize, count);
memoryCopyRangeSafe(dest, start, end, sizeMax); // copy with bounds check
memoryGetAllocatedCount() size_t // current malloc'd block count
```
---
## Math (`util/math.h`)
```c
mathNextPowTwo(value) uint32_t // next power of two >= value
mathMax(a, b) // macro
mathMin(a, b) // macro
mathClamp(x, lower, upper) // macro
mathAbs(amt) // macro
mathModFloat(x, y) float_t // always non-negative modulo
mathLerp(a, b, t) float_t // linear interpolation (floats)
```
For fixed-point lerp use `fixedLerp(a, b, t)` from `util/fixed.h`.
`MATH_PI` is defined as `M_PI`.
---
## Fixed-point (`util/fixed.h`)
Q24.8 format: 24-bit integer part, 8-bit fractional part (`int32_t`). See [architecture.md](architecture.md#fixed-point-math) for the full API.
Quick reference:
```c
FIXED(1.5f) // compile-time literal
fixedFromI32(3) // runtime int → fixed
fixedToFloat(f) // fixed → float (only for GL/platform APIs)
fixedMul(a, b) // multiplication (not just addition)
fixedDiv(a, b) // division
fixedLerp(a, b, t) // lerp where t ∈ [0, FIXED_ONE]
```
---
## Array (`util/array.h`)
```c
arrayReverse(array, count, size); // in-place reverse; size = sizeof(element)
```
---
## Sort (`util/sort.h`)
```c
sortQuick(array, count, size, compare); // quicksort
sortBubble(array, count, size, compare); // bubble sort (small arrays)
sort(array, count, size, compare); // macro alias for sortQuick
// Convenience uint8_t sorter:
sortArrayU8(array, count);
int sortArrayU8Compare(const void *a, const void *b); // comparator
```
`sortcompare_t` matches `qsort` comparator signature: `int (*)(const void *, const void *)`.
---
## Reference counting (`util/ref.h`)
```c
refInit(&ref, dataPtr, onLock, onUnlock, onAllUnlocked);
refLock(&ref); // increments count, calls onLock
bool_t hit_zero = refUnlock(&ref); // decrements; calls onAllUnlocked when 0
```
Used internally by the asset entry system to track how many callers hold a reference to a loaded asset entry.
---
## CRC32 (`util/crypt.h`)
```c
// One-shot:
uint32_t crc = cryptCRC32(data, size);
// Streaming:
uint32_t acc = cryptCRC32Begin();
cryptCRC32Update(&acc, chunk1, len1);
cryptCRC32Update(&acc, chunk2, len2);
uint32_t final = cryptCRC32End(acc);
```
Used by the save stream to checksum save files.
---
## Endian (`util/endian.h`)
All serialized data (save files, asset headers) is little-endian. Convert to/from host byte order:
```c
uint32_t val = endianLittleToHost32(rawU32);
uint16_t val = endianLittleToHost16(rawU16);
float_t val = endianLittleToHostFloat(rawFloat);
```
`isHostLittleEndian()` returns a bool at runtime. The compile-time defines `DUSK_PLATFORM_ENDIAN_LITTLE` / `DUSK_PLATFORM_ENDIAN_BIG` are set by `cmake/targets/<target>.cmake`.
+7 -64
View File
@@ -1,8 +1,11 @@
name: Build Dusk
on:
push:
tags:
- '*'
branches:
- main
pull_request:
branches:
- main
jobs:
run-tests:
runs-on: ubuntu-latest
@@ -50,22 +53,6 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
# build-vita:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
# - name: Set up Docker
# uses: docker/setup-docker-action@v5
# - name: Build Vita
# run: ./scripts/build-vita-docker.sh
# - name: Upload Vita binary
# uses: actions/upload-artifact@v6
# with:
# name: dusk-vita
# path: build-vita/Dusk.vpk
# if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
steps:
@@ -107,28 +94,6 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
build-gamecube-iso:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build GameCube ISO
run: ./scripts/build-gamecube-iso-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk
cp build-gamecube-iso/Dusk-NTSC-J.iso ./git-artifcats/Dusk/Dusk-NTSC-J.iso
cp build-gamecube-iso/Dusk-NTSC-U.iso ./git-artifcats/Dusk/Dusk-NTSC-U.iso
cp build-gamecube-iso/Dusk-PAL.iso ./git-artifcats/Dusk/Dusk-PAL.iso
- name: Upload GameCube ISO
uses: actions/upload-artifact@v6
with:
name: dusk-gamecube-iso
path: ./git-artifcats/Dusk
if-no-files-found: error
build-wii:
runs-on: ubuntu-latest
steps:
@@ -141,34 +106,12 @@ jobs:
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk/apps/Dusk
cp build-wii/boot.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
cp build-wii/Dusk.dol ./git-artifcats/Dusk/apps/Dusk/Dusk.dol
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
cp build-wii/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
cp docker/dolphin/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
- name: Upload Wii binary
uses: actions/upload-artifact@v6
with:
name: dusk-wii
path: ./git-artifcats/Dusk
if-no-files-found: error
build-wii-iso:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Wii ISO
run: ./scripts/build-wii-iso-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk
cp build-wii-iso/Dusk-NTSC-J.iso ./git-artifcats/Dusk/Dusk-NTSC-J.iso
cp build-wii-iso/Dusk-NTSC-U.iso ./git-artifcats/Dusk/Dusk-NTSC-U.iso
cp build-wii-iso/Dusk-PAL.iso ./git-artifcats/Dusk/Dusk-PAL.iso
- name: Upload Wii ISO
uses: actions/upload-artifact@v6
with:
name: dusk-wii-iso
path: ./git-artifcats/Dusk
if-no-files-found: error
+1 -3
View File
@@ -83,6 +83,7 @@ assets/borrowed
.VSCode*
/vita
._*
*~
@@ -104,6 +105,3 @@ yarn.lock
/build2
/build*
/assets/test
/tools_old
/assets/test.png
-65
View File
@@ -1,65 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
Dusk is a C11 RPG game targeting resource-constrained hardware (PSP, GameCube, Wii, PS Vita, Knulli handhelds) and Linux/OpenGL. All game code lives in `src/dusk/`; platform-specific backends live in `src/dusk{platform}/` (e.g. `src/duskgl/`, `src/duskpsp/`, `src/duskdolphin/`).
Assets are zipped into `dusk.dsk` at build time and loaded at runtime via the asset system.
## Build
```bash
# Linux (host)
./scripts/build-linux.sh # outputs build-linux/Dusk
# Other targets (require Docker)
./scripts/build-psp-docker.sh
./scripts/build-gamecube-docker.sh
./scripts/build-wii-docker.sh
./scripts/build-knulli-docker.sh
```
Each script is a thin wrapper around:
```bash
cmake -S . -B build-<target> -DDUSK_TARGET_SYSTEM=<target>
cmake --build build-<target> -- -j$(nproc)
```
## Tests
```bash
./scripts/test-linux.sh # builds and runs all tests
# Manually run a single test binary after building:
./build-tests/test/<module>/test_<name>
```
Tests use [cmocka](https://cmocka.org/) and are only compiled when `DUSK_BUILD_TESTS=ON`. Test sources live under `test/`.
## Key conventions
- Use `stringCompare`, `stringCopy`, `stringEquals`, etc. from `util/string.h` — never `strcmp`, `strcpy`, and friends directly.
- All functions that can fail return `errorret_t`. Use `errorThrow(...)`, `errorChain(call())`, and `errorOk()` macros — see `error/error.h`.
- Positions and game-world values use `fixed_t` (Q24.8 fixed-point, `int32_t`) — not `float`. Use `FIXED(x)` for literals and the `fixedFrom*`/`fixedTo*` helpers in `util/fixed.h`.
## Coding style
See [`.claude/style.md`](.claude/style.md) for the full style guide: indentation, line length, naming, typedefs, defines, include order, `const` usage, assertion placement, error handling, struct initialization, and platform conditionals.
## Architecture & systems
| Doc | Covers |
|---|---|
| [`.claude/architecture.md`](.claude/architecture.md) | Platform abstraction pattern, subsystem lifecycle, error handling, code-generation pipeline |
| [`.claude/display.md`](.claude/display.md) | Rendering pipeline: display state, screen, framebuffer, mesh, shader, texture, spritebatch, text |
| [`.claude/input.md`](.claude/input.md) | Input actions, buttons, bindings, axis helpers, events |
| [`.claude/asset.md`](.claude/asset.md) | Asset archive, entry lifecycle, loader types, async/sync split, low-level file I/O |
| [`.claude/ui.md`](.claude/ui.md) | UI element system, textbox, frames, loading overlay, fullbox transitions, FPS counter |
| [`.claude/animation.md`](.claude/animation.md) | Keyframe animation, easing functions |
| [`.claude/systems.md`](.claude/systems.md) | Time, threading, mutex, events, console, logging, system/platform, network |
| [`.claude/util.md`](.claude/util.md) | String, memory, math, fixed-point, array, sort, ref counting, CRC32, endian |
| [`.claude/save.md`](.claude/save.md) | Save slots, stream serialization with CRC, locale/i18n |
| [`.claude/rpg/index.md`](.claude/rpg/index.md) | RPG layer overview → [world](.claude/rpg/world.md), [entities](.claude/rpg/entity.md), [cutscenes](.claude/rpg/cutscene.md), [story/items](.claude/rpg/story.md) |
| [`.claude/display-refactor.md`](.claude/display-refactor.md) | Planned render-queue refactor (Saturn port context) |
+1 -18
View File
@@ -4,22 +4,14 @@
# https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
cmake_policy(SET CMP0079 NEW)
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
option(DUSK_BUILD_TESTS "Enable tests" OFF)
# Game identity — override these per-project
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
set(DUSK_GAME_AUTHOR "YouWish" CACHE STRING "Game author / coder")
set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description")
set(DUSK_GAME_LONG_DESCRIPTION "No description yet." CACHE STRING "Full description")
# Prep cache
set(DUSK_CACHE_TARGET "dusk-target")
@@ -76,19 +68,10 @@ else()
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" CACHE INTERNAL ${DUSK_CACHE_TARGET})
endif()
if(NOT DEFINED DUSK_VERSION)
string(TIMESTAMP DUSK_VERSION "debug-%y%m%d%H%M%S")
endif()
# Definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
DUSK_GAME_NAME="${DUSK_GAME_NAME}"
DUSK_GAME_AUTHOR="${DUSK_GAME_AUTHOR}"
DUSK_GAME_SHORT_DESCRIPTION="${DUSK_GAME_SHORT_DESCRIPTION}"
DUSK_GAME_LONG_DESCRIPTION="${DUSK_GAME_LONG_DESCRIPTION}"
DUSK_VERSION="${DUSK_VERSION}"
)
# Toolchains
-78
View File
@@ -1,78 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assettexture.h"
#include "asset/assettype.h"
#include "assert/assert.h"
#include "display/texture/texture.h"
#include "util/endian.h"
errorret_t assetTextureLoad(assetentire_t entire) {
assertNotNull(entire.data, "Data pointer cannot be NULL.");
assertNotNull(entire.output, "Output pointer cannot be NULL.");
assettexture_t *assetData = (assettexture_t *)entire.data;
texture_t *texture = (texture_t *)entire.output;
// Read header and version (first 4 bytes)
if(
assetData->header[0] != 'D' ||
assetData->header[1] != 'T' ||
assetData->header[2] != 'X'
) {
errorThrow("Invalid texture header");
}
// Version (can only be 1 atm)
if(assetData->version != 0x01) {
errorThrow("Unsupported texture version");
}
// Fix endian
assetData->width = endianLittleToHost32(assetData->width);
assetData->height = endianLittleToHost32(assetData->height);
// Check dimensions.
if(
assetData->width == 0 || assetData->width > ASSET_TEXTURE_WIDTH_MAX ||
assetData->height == 0 || assetData->height > ASSET_TEXTURE_HEIGHT_MAX
) {
errorThrow("Invalid texture dimensions");
}
// Validate format
textureformat_t format;
texturedata_t data;
switch(assetData->type) {
case 0x00: // RGBA8888
format = TEXTURE_FORMAT_RGBA;
data.rgbaColors = (color_t *)assetData->data;
break;
// case 0x01:
// format = TEXTURE_FORMAT_RGB;
// break;
// case 0x02:
// format = TEXTURE_FORMAT_RGB565;
// break;
// case 0x03:
// format = TEXTURE_FORMAT_RGB5A3;
// break;
default:
errorThrow("Unsupported texture format");
}
errorChain(textureInit(
texture, assetData->width, assetData->height, format, data
));
errorOk();
}
@@ -1,28 +0,0 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
display.c
)
# Subdirectories
add_subdirectory(framebuffer)
add_subdirectory(mesh)
add_subdirectory(screen)
add_subdirectory(shader)
add_subdirectory(spritebatch)
add_subdirectory(text)
add_subdirectory(texture)
# Color definitions
dusk_run_python(
dusk_color_defs
tools.color.csv
--csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h
)
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_color_defs)
@@ -1,23 +0,0 @@
name,r,g,b,a
black,0,0,0,1
white,1,1,1,1
red,1,0,0,1
green,0,1,0,1
blue,0,0,1,1
yellow,1,1,0,1
cyan,0,1,1,1
magenta,1,0,1,1
transparent,0,0,0,0
transparent_white,1,1,1,0
transparent_black,0,0,0,0
gray,0.5,0.5,0.5,1
light_gray,0.75,0.75,0.75,1
dark_gray,0.25,0.25,0.25,1
orange,1,0.65,0,1
purple,0.5,0,0.5,1
brown,0.6,0.4,0.2,1
pink,1,0.75,0.8,1
lime,0.75,1,0,1
navy,0,0,0.5,1
teal,0,0.5,0.5,1
cornflower_blue,0.39,0.58,0.93,1
1 name r g b a
2 black 0 0 0 1
3 white 1 1 1 1
4 red 1 0 0 1
5 green 0 1 0 1
6 blue 0 0 1 1
7 yellow 1 1 0 1
8 cyan 0 1 1 1
9 magenta 1 0 1 1
10 transparent 0 0 0 0
11 transparent_white 1 1 1 0
12 transparent_black 0 0 0 0
13 gray 0.5 0.5 0.5 1
14 light_gray 0.75 0.75 0.75 1
15 dark_gray 0.25 0.25 0.25 1
16 orange 1 0.65 0 1
17 purple 0.5 0 0.5 1
18 brown 0.6 0.4 0.2 1
19 pink 1 0.75 0.8 1
20 lime 0.75 1 0 1
21 navy 0 0 0.5 1
22 teal 0 0.5 0.5 1
23 cornflower_blue 0.39 0.58 0.93 1
@@ -1,116 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/display.h"
#include "display/framebuffer/framebuffer.h"
#include "scene/scene.h"
#include "display/spritebatch/spritebatch.h"
#include "display/mesh/quad.h"
#include "display/mesh/cube.h"
#include "display/mesh/sphere.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
#include "display/mesh/triprism.h"
#include "display/screen/screen.h"
#include "ui/ui.h"
#include "display/text/text.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "asset/asset.h"
#include "display/shader/shaderlist.h"
#include "time/time.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
memoryZero(&DISPLAY, sizeof(DISPLAY));
#ifdef displayPlatformInit
errorChain(displayPlatformInit());
#endif
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
errorChain(textureInit(
&TEXTURE_WHITE, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
));
errorChain(textureInit(
&TEXTURE_TEST, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
));
// Standard meshes
errorChain(quadInit());
errorChain(cubeInit());
errorChain(sphereInit());
errorChain(planeInit());
errorChain(capsuleInit());
errorChain(triPrismInit());
errorChain(frameBufferInitBackBuffer());
errorChain(spriteBatchInit());
errorChain(textInit());
errorChain(screenInit());
// Setup initial shader with default values
errorChain(shaderListInit());
errorOk();
}
errorret_t displayUpdate(void) {
#ifdef displayPlatformUpdate
errorChain(displayPlatformUpdate());
#endif
// Reset state
spriteBatchClear();
errorChain(frameBufferBind(NULL));
// Bind screen and render scene
errorChain(screenBind());
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
SCREEN.background
);
errorChain(sceneRender());
// Finish up
screenUnbind();
screenRender();
// Swap and return.
#ifdef displayPlatformSwap
errorChain(displayPlatformSwap());
#endif
errorOk();
}
errorret_t displaySetState(displaystate_t state) {
#ifdef displayPlatformSetState
errorChain(displayPlatformSetState(state));
#endif
errorOk();
}
errorret_t displayDispose(void) {
errorChain(shaderListDispose());
errorChain(spriteBatchDispose());
screenDispose();
errorChain(textDispose());
errorChain(textureDispose(&TEXTURE_WHITE));
errorChain(textureDispose(&TEXTURE_TEST));
#ifdef displayPlatformDispose
displayPlatformDispose();
#endif
// For now, we just return an OK error.
errorOk();
}
@@ -1,65 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/displayplatform.h"
// Expecting some definitions to be provided
#ifndef DUSK_DISPLAY_SIZE_DYNAMIC
#ifndef DUSK_DISPLAY_WIDTH
#error "DUSK_DISPLAY_WIDTH must be defined."
#endif
#ifndef DUSK_DISPLAY_HEIGHT
#error "DUSK_DISPLAY_HEIGHT must be defined"
#endif
#define DUSK_DISPLAY_WIDTH_DEFAULT DUSK_DISPLAY_WIDTH
#define DUSK_DISPLAY_HEIGHT_DEFAULT DUSK_DISPLAY_HEIGHT
#else
#ifndef DUSK_DISPLAY_WIDTH_DEFAULT
#error "DUSK_DISPLAY_WIDTH_DEFAULT must be defined."
#endif
#ifndef DUSK_DISPLAY_HEIGHT_DEFAULT
#error "DUSK_DISPLAY_HEIGHT_DEFAULT must be defined."
#endif
#ifdef DUSK_DISPLAY_WIDTH
#error "DUSK_DISPLAY_WIDTH should not be defined."
#endif
#ifdef DUSK_DISPLAY_HEIGHT
#error "DUSK_DISPLAY_HEIGHT should not be defined."
#endif
#endif
// Main Display Struct, platform-speicifc
typedef displayplatform_t display_t;
extern display_t DISPLAY;
/**
* Initializes the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayInit(void);
/**
* Tells the display system to actually draw the frame.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayUpdate(void);
/**
* Sets the display state.
*
* @param state The state to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySetState(displaystate_t state);
/**
* Disposes of the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayDispose(void);
@@ -1,17 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define DISPLAY_STATE_FLAG_CULL (1 << 0)
#define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1)
#define DISPLAY_STATE_FLAG_BLEND (1 << 2)
typedef struct {
uint8_t flags;
} displaystate_t;
@@ -1,192 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "capsule.h"
#include "assert/assert.h"
mesh_t CAPSULE_MESH_SIMPLE;
meshvertex_t CAPSULE_MESH_SIMPLE_VERTICES[CAPSULE_VERTEX_COUNT];
errorret_t capsuleInit() {
vec3 center = { 0.0f, 0.0f, 0.0f };
capsuleBuffer(
CAPSULE_MESH_SIMPLE_VERTICES,
center,
0.5f,
0.5f,
CAPSULE_CAP_RINGS,
CAPSULE_SECTORS
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
errorChain(meshInit(
&CAPSULE_MESH_SIMPLE,
CAPSULE_PRIMITIVE_TYPE,
CAPSULE_VERTEX_COUNT,
CAPSULE_MESH_SIMPLE_VERTICES
));
errorOk();
}
void capsuleBuffer(
meshvertex_t *vertices,
const vec3 center,
const float_t radius,
const float_t halfHeight,
const int32_t capRings,
const int32_t sectors
#if MESH_ENABLE_COLOR
, const color_t color
#endif
) {
assertNotNull(vertices, "Vertices cannot be NULL");
assertNotNull(center, "Center vector cannot be NULL");
const float_t cx = center[0];
const float_t cy = center[1];
const float_t cz = center[2];
const float_t sectorStep = 2.0f * (float_t)GLM_PI / (float_t)sectors;
int32_t vi = 0;
/* Helper macro: write one vertex. */
#if MESH_ENABLE_COLOR
#define CAP_VERT(px, py, pz, u, v) \
vertices[vi].color = color; \
vertices[vi].pos[0] = (px); \
vertices[vi].pos[1] = (py); \
vertices[vi].pos[2] = (pz); \
vertices[vi].uv[0] = (u); \
vertices[vi].uv[1] = (v); \
vi++;
#else
#define CAP_VERT(px, py, pz, u, v) \
vertices[vi].pos[0] = (px); \
vertices[vi].pos[1] = (py); \
vertices[vi].pos[2] = (pz); \
vertices[vi].uv[0] = (u); \
vertices[vi].uv[1] = (v); \
vi++;
#endif
/* ---- Top hemisphere ---- */
/* phi ranges from PI/2 (top pole) down to 0 (equator). */
const float_t capStep = (float_t)GLM_PI_2 / (float_t)capRings;
for(int32_t i = 0; i < capRings; i++) {
const float_t phi1 = (float_t)GLM_PI_2 - (float_t)i * capStep;
const float_t phi2 = (float_t)GLM_PI_2 - (float_t)(i + 1) * capStep;
const float_t ly1 = radius * sinf(phi1);
const float_t ly2 = radius * sinf(phi2);
const float_t lxz1 = radius * cosf(phi1);
const float_t lxz2 = radius * cosf(phi2);
/* UV: top cap occupies v in [0.5 + halfHeightFrac .. 1.0]: we use a
* simple per-band normalisation against the full height. */
const float_t v1 = 1.0f - (float_t)i / (float_t)(2 * capRings + 1);
const float_t v2 = 1.0f - (float_t)(i + 1) / (float_t)(2 * capRings + 1);
for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep;
const float_t t2 = (float_t)(j + 1) * sectorStep;
const float_t u1 = (float_t)j / (float_t)sectors;
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
const float_t x11 = lxz1 * cosf(t1), z11 = lxz1 * sinf(t1);
const float_t x12 = lxz1 * cosf(t2), z12 = lxz1 * sinf(t2);
const float_t x21 = lxz2 * cosf(t1), z21 = lxz2 * sinf(t1);
const float_t x22 = lxz2 * cosf(t2), z22 = lxz2 * sinf(t2);
const float_t y1off = cy + halfHeight + ly1;
const float_t y2off = cy + halfHeight + ly2;
CAP_VERT(cx+x11, y1off, cz+z11, u1, v1)
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
CAP_VERT(cx+x22, y2off, cz+z22, u2, v2)
}
}
/* ---- Cylindrical body ---- */
{
const float_t yTop = cy + halfHeight;
const float_t yBot = cy - halfHeight;
const float_t vTop = (
1.0f - (float_t)capRings / (float_t)(2 * capRings + 1)
);
const float_t vBot = (
1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1)
);
for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep;
const float_t t2 = (float_t)(j + 1) * sectorStep;
const float_t u1 = (float_t)j / (float_t)sectors;
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
const float_t x1 = radius * cosf(t1), z1 = radius * sinf(t1);
const float_t x2 = radius * cosf(t2), z2 = radius * sinf(t2);
CAP_VERT(cx+x1, yTop, cz+z1, u1, vTop)
CAP_VERT(cx+x1, yBot, cz+z1, u1, vBot)
CAP_VERT(cx+x2, yTop, cz+z2, u2, vTop)
CAP_VERT(cx+x2, yTop, cz+z2, u2, vTop)
CAP_VERT(cx+x1, yBot, cz+z1, u1, vBot)
CAP_VERT(cx+x2, yBot, cz+z2, u2, vBot)
}
}
// Bottom hemisphere
for(int32_t i = 0; i < capRings; i++) {
const float_t phi1 = -(float_t)i * capStep;
const float_t phi2 = -(float_t)(i + 1) * capStep;
const float_t ly1 = radius * sinf(phi1);
const float_t ly2 = radius * sinf(phi2);
const float_t lxz1 = radius * cosf(phi1);
const float_t lxz2 = radius * cosf(phi2);
const float_t v1 = (
1.0f - (float_t)(capRings + 1 + i) / (float_t)(2 * capRings + 1)
);
const float_t v2 = (
1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1)
);
for(int32_t j = 0; j < sectors; j++) {
const float_t t1 = (float_t)j * sectorStep;
const float_t t2 = (float_t)(j + 1) * sectorStep;
const float_t u1 = (float_t)j / (float_t)sectors;
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
const float_t x11 = lxz1 * cosf(t1), z11 = lxz1 * sinf(t1);
const float_t x12 = lxz1 * cosf(t2), z12 = lxz1 * sinf(t2);
const float_t x21 = lxz2 * cosf(t1), z21 = lxz2 * sinf(t1);
const float_t x22 = lxz2 * cosf(t2), z22 = lxz2 * sinf(t2);
const float_t y1off = cy - halfHeight + ly1;
const float_t y2off = cy - halfHeight + ly2;
CAP_VERT(cx+x11, y1off, cz+z11, u1, v1)
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
CAP_VERT(cx+x22, y2off, cz+z22, u2, v2)
}
}
#undef CAP_VERT
}
@@ -1,52 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/mesh.h"
#include "display/color.h"
#define CAPSULE_CAP_RINGS 4
#define CAPSULE_SECTORS 16
#define CAPSULE_VERTEX_COUNT ((2 * CAPSULE_CAP_RINGS + 1) * CAPSULE_SECTORS * 6)
#define CAPSULE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
extern mesh_t CAPSULE_MESH_SIMPLE;
extern meshvertex_t CAPSULE_MESH_SIMPLE_VERTICES[CAPSULE_VERTEX_COUNT];
/**
* Initializes the simple unit capsule mesh centered at (0,0,0) with radius 0.5
* and a cylindrical half-height of 0.5 (total height 2.0).
*
* @return Error for initialization of the capsule mesh.
*/
errorret_t capsuleInit();
/**
* Buffers a capsule (cylinder + two hemisphere caps) into the provided vertex
* array. The capsule's long axis is Y. Total vertex count is
* (2*capRings + 1) * sectors * 6.
*
* @param vertices Vertex array to write into.
* @param center Center position of the capsule.
* @param radius Radius of the cylinder and hemisphere caps.
* @param halfHeight Half the height of the cylindrical section only (caps
* extend an additional radius above/below).
* @param capRings Number of latitude rings per hemisphere cap.
* @param sectors Number of longitude segments around the circumference.
* @param color Color applied to all vertices.
*/
void capsuleBuffer(
meshvertex_t *vertices,
const vec3 center,
const float_t radius,
const float_t halfHeight,
const int32_t capRings,
const int32_t sectors
#if MESH_ENABLE_COLOR
, const color_t color
#endif
);
@@ -1,114 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "cube.h"
#include "assert/assert.h"
mesh_t CUBE_MESH_SIMPLE;
meshvertex_t CUBE_MESH_SIMPLE_VERTICES[CUBE_VERTEX_COUNT];
errorret_t cubeInit() {
vec3 min = { 0.0f, 0.0f, 0.0f };
vec3 max = { 1.0f, 1.0f, 1.0f };
cubeBuffer(
CUBE_MESH_SIMPLE_VERTICES, min, max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
errorChain(meshInit(
&CUBE_MESH_SIMPLE,
CUBE_PRIMITIVE_TYPE,
CUBE_VERTEX_COUNT,
CUBE_MESH_SIMPLE_VERTICES
));
errorOk();
}
// Helper macro: set one vertex position, UV and color.
#if MESH_ENABLE_COLOR
#define CUBE_VERT(i, px, py, pz, u, v) \
vertices[i].color = color; \
vertices[i].pos[0] = (px); \
vertices[i].pos[1] = (py); \
vertices[i].pos[2] = (pz); \
vertices[i].uv[0] = (u); \
vertices[i].uv[1] = (v);
#else
#define CUBE_VERT(i, px, py, pz, u, v) \
vertices[i].pos[0] = (px); \
vertices[i].pos[1] = (py); \
vertices[i].pos[2] = (pz); \
vertices[i].uv[0] = (u); \
vertices[i].uv[1] = (v);
#endif
void cubeBuffer(
meshvertex_t *vertices,
const vec3 min,
const vec3 max
#if MESH_ENABLE_COLOR
, const color_t color
#endif
) {
assertNotNull(vertices, "Vertices cannot be NULL");
assertNotNull(min, "Min vector cannot be NULL");
assertNotNull(max, "Max vector cannot be NULL");
const float_t x0 = min[0], y0 = min[1], z0 = min[2];
const float_t x1 = max[0], y1 = max[1], z1 = max[2];
// Front face (+Z normal): CCW when viewed from +Z
CUBE_VERT( 0, x0, y0, z1, 0.0f, 0.0f);
CUBE_VERT( 1, x1, y0, z1, 1.0f, 0.0f);
CUBE_VERT( 2, x1, y1, z1, 1.0f, 1.0f);
CUBE_VERT( 3, x0, y0, z1, 0.0f, 0.0f);
CUBE_VERT( 4, x1, y1, z1, 1.0f, 1.0f);
CUBE_VERT( 5, x0, y1, z1, 0.0f, 1.0f);
// Back face (-Z normal): CCW when viewed from -Z
CUBE_VERT( 6, x1, y0, z0, 0.0f, 0.0f);
CUBE_VERT( 7, x0, y0, z0, 1.0f, 0.0f);
CUBE_VERT( 8, x0, y1, z0, 1.0f, 1.0f);
CUBE_VERT( 9, x1, y0, z0, 0.0f, 0.0f);
CUBE_VERT(10, x0, y1, z0, 1.0f, 1.0f);
CUBE_VERT(11, x1, y1, z0, 0.0f, 1.0f);
// Right face (+X normal): CCW when viewed from +X
CUBE_VERT(12, x1, y0, z1, 0.0f, 0.0f);
CUBE_VERT(13, x1, y0, z0, 1.0f, 0.0f);
CUBE_VERT(14, x1, y1, z0, 1.0f, 1.0f);
CUBE_VERT(15, x1, y0, z1, 0.0f, 0.0f);
CUBE_VERT(16, x1, y1, z0, 1.0f, 1.0f);
CUBE_VERT(17, x1, y1, z1, 0.0f, 1.0f);
// Left face (-X normal): CCW when viewed from -X
CUBE_VERT(18, x0, y0, z0, 0.0f, 0.0f);
CUBE_VERT(19, x0, y0, z1, 1.0f, 0.0f);
CUBE_VERT(20, x0, y1, z1, 1.0f, 1.0f);
CUBE_VERT(21, x0, y0, z0, 0.0f, 0.0f);
CUBE_VERT(22, x0, y1, z1, 1.0f, 1.0f);
CUBE_VERT(23, x0, y1, z0, 0.0f, 1.0f);
// Top face (+Y normal): CCW when viewed from +Y
CUBE_VERT(24, x0, y1, z1, 0.0f, 0.0f);
CUBE_VERT(25, x1, y1, z1, 1.0f, 0.0f);
CUBE_VERT(26, x1, y1, z0, 1.0f, 1.0f);
CUBE_VERT(27, x0, y1, z1, 0.0f, 0.0f);
CUBE_VERT(28, x1, y1, z0, 1.0f, 1.0f);
CUBE_VERT(29, x0, y1, z0, 0.0f, 1.0f);
// Bottom face (-Y normal): CCW when viewed from -Y
CUBE_VERT(30, x0, y0, z0, 0.0f, 0.0f);
CUBE_VERT(31, x1, y0, z0, 1.0f, 0.0f);
CUBE_VERT(32, x1, y0, z1, 1.0f, 1.0f);
CUBE_VERT(33, x0, y0, z0, 0.0f, 0.0f);
CUBE_VERT(34, x1, y0, z1, 1.0f, 1.0f);
CUBE_VERT(35, x0, y0, z1, 0.0f, 1.0f);
#undef CUBE_VERT
}
@@ -1,43 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/mesh.h"
#include "display/color.h"
#define CUBE_FACE_COUNT 6
#define CUBE_VERTICES_PER_FACE 6
#define CUBE_VERTEX_COUNT (CUBE_FACE_COUNT * CUBE_VERTICES_PER_FACE)
#define CUBE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
extern mesh_t CUBE_MESH_SIMPLE;
extern meshvertex_t CUBE_MESH_SIMPLE_VERTICES[CUBE_VERTEX_COUNT];
/**
* Initializes the simple unit cube mesh (0,0,0) to (1,1,1).
*
* @return Error for initialization of the cube mesh.
*/
errorret_t cubeInit();
/**
* Buffers a 3D axis-aligned cube into the provided vertex array.
* Writes CUBE_VERTEX_COUNT vertices (6 faces x 6 vertices, CCW winding).
*
* @param vertices The vertex array to buffer into.
* @param min The minimum XYZ corner of the cube.
* @param max The maximum XYZ corner of the cube.
* @param color The color applied to all vertices.
*/
void cubeBuffer(
meshvertex_t *vertices,
const vec3 min,
const vec3 max
#if MESH_ENABLE_COLOR
, const color_t color
#endif
);
@@ -1,116 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "plane.h"
#include "assert/assert.h"
mesh_t PLANE_MESH_SIMPLE;
meshvertex_t PLANE_MESH_SIMPLE_VERTICES[PLANE_VERTEX_COUNT];
errorret_t planeInit() {
vec3 min = { 0.0f, 0.0f, 0.0f };
vec3 max = { 1.0f, 0.0f, 1.0f };
vec2 uvMin = { 0.0f, 0.0f };
vec2 uvMax = { 1.0f, 1.0f };
planeBuffer(
PLANE_MESH_SIMPLE_VERTICES,
PLANE_AXIS_XZ,
min,
max
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
, uvMin,
uvMax
);
errorChain(meshInit(
&PLANE_MESH_SIMPLE,
PLANE_PRIMITIVE_TYPE,
PLANE_VERTEX_COUNT,
PLANE_MESH_SIMPLE_VERTICES
));
errorOk();
}
/* Helper macro to write one vertex. */
#if MESH_ENABLE_COLOR
#define PLANE_VERT(i, px, py, pz, u, v) \
vertices[i].color = color; \
vertices[i].pos[0] = (px); \
vertices[i].pos[1] = (py); \
vertices[i].pos[2] = (pz); \
vertices[i].uv[0] = (u); \
vertices[i].uv[1] = (v);
#else
#define PLANE_VERT(i, px, py, pz, u, v) \
vertices[i].pos[0] = (px); \
vertices[i].pos[1] = (py); \
vertices[i].pos[2] = (pz); \
vertices[i].uv[0] = (u); \
vertices[i].uv[1] = (v);
#endif
void planeBuffer(
meshvertex_t *vertices,
const planeaxis_t axis,
const vec3 min,
const vec3 max
#if MESH_ENABLE_COLOR
, const color_t color
#endif
, const vec2 uvMin,
const vec2 uvMax
) {
assertNotNull(vertices, "Vertices cannot be NULL");
assertNotNull(min, "Min vector cannot be NULL");
assertNotNull(max, "Max vector cannot be NULL");
assertNotNull(uvMin, "uvMin cannot be NULL");
assertNotNull(uvMax, "uvMax cannot be NULL");
const float_t u0 = uvMin[0], u1 = uvMax[0];
const float_t v0 = uvMin[1], v1 = uvMax[1];
switch(axis) {
case PLANE_AXIS_XY: {
/* Flat in XY at z = min[2]; spans X and Y. */
const float_t z = min[2];
PLANE_VERT(0, min[0], min[1], z, u0, v0)
PLANE_VERT(1, max[0], min[1], z, u1, v0)
PLANE_VERT(2, max[0], max[1], z, u1, v1)
PLANE_VERT(3, min[0], min[1], z, u0, v0)
PLANE_VERT(4, max[0], max[1], z, u1, v1)
PLANE_VERT(5, min[0], max[1], z, u0, v1)
break;
}
case PLANE_AXIS_XZ: {
/* Flat in XZ at y = min[1]; spans X and Z. */
const float_t y = min[1];
PLANE_VERT(0, min[0], y, min[2], u0, v0)
PLANE_VERT(1, max[0], y, min[2], u1, v0)
PLANE_VERT(2, max[0], y, max[2], u1, v1)
PLANE_VERT(3, min[0], y, min[2], u0, v0)
PLANE_VERT(4, max[0], y, max[2], u1, v1)
PLANE_VERT(5, min[0], y, max[2], u0, v1)
break;
}
case PLANE_AXIS_YZ: {
/* Flat in YZ at x = min[0]; spans Y and Z. */
const float_t x = min[0];
PLANE_VERT(0, x, min[1], min[2], u0, v0)
PLANE_VERT(1, x, max[1], min[2], u1, v0)
PLANE_VERT(2, x, max[1], max[2], u1, v1)
PLANE_VERT(3, x, min[1], min[2], u0, v0)
PLANE_VERT(4, x, max[1], max[2], u1, v1)
PLANE_VERT(5, x, min[1], max[2], u0, v1)
break;
}
}
#undef PLANE_VERT
}
@@ -1,61 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/mesh.h"
#include "display/color.h"
#define PLANE_VERTEX_COUNT 6
#define PLANE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
/** Which axis the plane's normal points along. */
typedef enum {
PLANE_AXIS_XY = 0, /**< Flat in XY, normal along +Z (billboard/wall face). */
PLANE_AXIS_XZ = 1, /**< Flat in XZ, normal along +Y (ground/floor plane). */
PLANE_AXIS_YZ = 2, /**< Flat in YZ, normal along +X (side wall). */
} planeaxis_t;
extern mesh_t PLANE_MESH_SIMPLE;
extern meshvertex_t PLANE_MESH_SIMPLE_VERTICES[PLANE_VERTEX_COUNT];
/**
* Initializes the simple unit XZ plane mesh (ground plane) from (0,0,0) to
* (1,0,1).
*
* @return Error for initialization of the plane mesh.
*/
errorret_t planeInit();
/**
* Buffers an axis-aligned plane into the provided vertex array.
* Writes PLANE_VERTEX_COUNT (6) vertices (two triangles, CCW winding).
*
* The min/max corners fully describe the plane in 3D space. The axis enum
* controls which dimension is treated as the "depth" (normal) axis:
* PLANE_AXIS_XY: spans X and Y, depth from min[2]/max[2] (uses min[2])
* PLANE_AXIS_XZ: spans X and Z, depth from min[1]/max[1] (uses min[1])
* PLANE_AXIS_YZ: spans Y and Z, depth from min[0]/max[0] (uses min[0])
*
* @param vertices Vertex array to write into (must hold PLANE_VERTEX_COUNT).
* @param axis Which axis the plane's normal points along.
* @param min Minimum XYZ corner.
* @param max Maximum XYZ corner.
* @param color Color applied to all vertices.
* @param uvMin Minimum UV coordinates.
* @param uvMax Maximum UV coordinates.
*/
void planeBuffer(
meshvertex_t *vertices,
const planeaxis_t axis,
const vec3 min,
const vec3 max
#if MESH_ENABLE_COLOR
, const color_t color
#endif
, const vec2 uvMin,
const vec2 uvMax
);
@@ -1,145 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "sphere.h"
#include "assert/assert.h"
mesh_t SPHERE_MESH_SIMPLE;
meshvertex_t SPHERE_MESH_SIMPLE_VERTICES[SPHERE_VERTEX_COUNT];
errorret_t sphereInit() {
vec3 center = { 0.0f, 0.0f, 0.0f };
sphereBuffer(
SPHERE_MESH_SIMPLE_VERTICES,
center,
0.5f,
SPHERE_STACKS,
SPHERE_SECTORS
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
errorChain(meshInit(
&SPHERE_MESH_SIMPLE,
SPHERE_PRIMITIVE_TYPE,
SPHERE_VERTEX_COUNT,
SPHERE_MESH_SIMPLE_VERTICES
));
errorOk();
}
void sphereBuffer(
meshvertex_t *vertices,
const vec3 center,
const float_t radius,
const int32_t stacks,
const int32_t sectors
#if MESH_ENABLE_COLOR
, const color_t color
#endif
) {
assertNotNull(vertices, "Vertices cannot be NULL");
assertNotNull(center, "Center vector cannot be NULL");
const float_t stackStep = (float_t)GLM_PI / (float_t)stacks;
const float_t sectorStep = 2.0f * (float_t)GLM_PI / (float_t)sectors;
int32_t vi = 0;
for(int32_t i = 0; i < stacks; i++) {
/* Latitude angles: top of band -> bottom of band */
const float_t phi1 = (float_t)GLM_PI_2 - (float_t)i * stackStep;
const float_t phi2 = (float_t)GLM_PI_2 - (float_t)(i + 1) * stackStep;
const float_t y1 = radius * sinf(phi1);
const float_t y2 = radius * sinf(phi2);
const float_t xz1 = radius * cosf(phi1);
const float_t xz2 = radius * cosf(phi2);
const float_t v1 = 1.0f - (float_t)i / (float_t)stacks;
const float_t v2 = 1.0f - (float_t)(i + 1) / (float_t)stacks;
for(int32_t j = 0; j < sectors; j++) {
const float_t theta1 = (float_t)j * sectorStep;
const float_t theta2 = (float_t)(j + 1) * sectorStep;
const float_t x11 = xz1 * cosf(theta1);
const float_t z11 = xz1 * sinf(theta1);
const float_t x12 = xz1 * cosf(theta2);
const float_t z12 = xz1 * sinf(theta2);
const float_t x21 = xz2 * cosf(theta1);
const float_t z21 = xz2 * sinf(theta1);
const float_t x22 = xz2 * cosf(theta2);
const float_t z22 = xz2 * sinf(theta2);
const float_t u1 = (float_t)j / (float_t)sectors;
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
/* Triangle 1: top-left, bottom-left, top-right */
#if MESH_ENABLE_COLOR
vertices[vi].color = color;
#endif
vertices[vi].pos[0] = center[0] + x11;
vertices[vi].pos[1] = center[1] + y1;
vertices[vi].pos[2] = center[2] + z11;
vertices[vi].uv[0] = u1;
vertices[vi].uv[1] = v1;
vi++;
#if MESH_ENABLE_COLOR
vertices[vi].color = color;
#endif
vertices[vi].pos[0] = center[0] + x21;
vertices[vi].pos[1] = center[1] + y2;
vertices[vi].pos[2] = center[2] + z21;
vertices[vi].uv[0] = u1;
vertices[vi].uv[1] = v2;
vi++;
#if MESH_ENABLE_COLOR
vertices[vi].color = color;
#endif
vertices[vi].pos[0] = center[0] + x12;
vertices[vi].pos[1] = center[1] + y1;
vertices[vi].pos[2] = center[2] + z12;
vertices[vi].uv[0] = u2;
vertices[vi].uv[1] = v1;
vi++;
/* Triangle 2: top-right, bottom-left, bottom-right */
#if MESH_ENABLE_COLOR
vertices[vi].color = color;
#endif
vertices[vi].pos[0] = center[0] + x12;
vertices[vi].pos[1] = center[1] + y1;
vertices[vi].pos[2] = center[2] + z12;
vertices[vi].uv[0] = u2;
vertices[vi].uv[1] = v1;
vi++;
#if MESH_ENABLE_COLOR
vertices[vi].color = color;
#endif
vertices[vi].pos[0] = center[0] + x21;
vertices[vi].pos[1] = center[1] + y2;
vertices[vi].pos[2] = center[2] + z21;
vertices[vi].uv[0] = u1;
vertices[vi].uv[1] = v2;
vi++;
#if MESH_ENABLE_COLOR
vertices[vi].color = color;
#endif
vertices[vi].pos[0] = center[0] + x22;
vertices[vi].pos[1] = center[1] + y2;
vertices[vi].pos[2] = center[2] + z22;
vertices[vi].uv[0] = u2;
vertices[vi].uv[1] = v2;
vi++;
}
}
}
@@ -1,47 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/mesh.h"
#include "display/color.h"
#define SPHERE_STACKS 8
#define SPHERE_SECTORS 16
#define SPHERE_VERTEX_COUNT (SPHERE_STACKS * SPHERE_SECTORS * 6)
#define SPHERE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
extern mesh_t SPHERE_MESH_SIMPLE;
extern meshvertex_t SPHERE_MESH_SIMPLE_VERTICES[SPHERE_VERTEX_COUNT];
/**
* Initializes the simple unit sphere mesh centered at (0,0,0) with radius 0.5.
*
* @return Error for initialization of the sphere mesh.
*/
errorret_t sphereInit();
/**
* Buffers a UV sphere into the provided vertex array.
* Writes stacks * sectors * 6 vertices (CCW winding).
*
* @param vertices Vertex array to write into (must hold stacks*sectors*6).
* @param center Center position of the sphere.
* @param radius Radius of the sphere.
* @param stacks Number of horizontal rings (latitude bands).
* @param sectors Number of vertical segments (longitude slices).
* @param color Color applied to all vertices.
*/
void sphereBuffer(
meshvertex_t *vertices,
const vec3 center,
const float_t radius,
const int32_t stacks,
const int32_t sectors
#if MESH_ENABLE_COLOR
, const color_t color
#endif
);
@@ -1,106 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "triprism.h"
#include "assert/assert.h"
mesh_t TRIPRISM_MESH_SIMPLE;
meshvertex_t TRIPRISM_MESH_SIMPLE_VERTICES[TRIPRISM_VERTEX_COUNT];
errorret_t triPrismInit() {
triPrismBuffer(
TRIPRISM_MESH_SIMPLE_VERTICES,
0.0f, 0.0f, /* p0: bottom-left */
1.0f, 0.0f, /* p1: bottom-right */
0.5f, 1.0f, /* p2: apex */
0.0f, 1.0f /* minZ, maxZ */
#if MESH_ENABLE_COLOR
, COLOR_WHITE_4B
#endif
);
errorChain(meshInit(
&TRIPRISM_MESH_SIMPLE,
TRIPRISM_PRIMITIVE_TYPE,
TRIPRISM_VERTEX_COUNT,
TRIPRISM_MESH_SIMPLE_VERTICES
));
errorOk();
}
void triPrismBuffer(
meshvertex_t *vertices,
const float_t x0, const float_t y0,
const float_t x1, const float_t y1,
const float_t x2, const float_t y2,
const float_t minZ,
const float_t maxZ
#if MESH_ENABLE_COLOR
, const color_t color
#endif
) {
assertNotNull(vertices, "Vertices cannot be NULL");
/* Helper macro: write one vertex then advance index. */
int32_t vi = 0;
#if MESH_ENABLE_COLOR
#define PRISM_VERT(px, py, pz, u, v) \
vertices[vi].color = color; \
vertices[vi].pos[0] = (px); \
vertices[vi].pos[1] = (py); \
vertices[vi].pos[2] = (pz); \
vertices[vi].uv[0] = (u); \
vertices[vi].uv[1] = (v); \
vi++;
#else
#define PRISM_VERT(px, py, pz, u, v) \
vertices[vi].pos[0] = (px); \
vertices[vi].pos[1] = (py); \
vertices[vi].pos[2] = (pz); \
vertices[vi].uv[0] = (u); \
vertices[vi].uv[1] = (v); \
vi++;
#endif
/* --- Front face (z = maxZ), CCW from +Z --- */
PRISM_VERT(x0, y0, maxZ, 0.0f, 0.0f)
PRISM_VERT(x1, y1, maxZ, 1.0f, 0.0f)
PRISM_VERT(x2, y2, maxZ, 0.5f, 1.0f)
/* --- Back face (z = minZ), CCW from -Z (reverse winding) --- */
PRISM_VERT(x2, y2, minZ, 0.5f, 1.0f)
PRISM_VERT(x1, y1, minZ, 1.0f, 0.0f)
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
/* --- Side face 0: edge p0->p1 --- */
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
PRISM_VERT(x1, y1, minZ, 1.0f, 0.0f)
PRISM_VERT(x1, y1, maxZ, 1.0f, 1.0f)
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
PRISM_VERT(x1, y1, maxZ, 1.0f, 1.0f)
PRISM_VERT(x0, y0, maxZ, 0.0f, 1.0f)
/* --- Side face 1: edge p1->p2 --- */
PRISM_VERT(x1, y1, minZ, 0.0f, 0.0f)
PRISM_VERT(x2, y2, minZ, 1.0f, 0.0f)
PRISM_VERT(x2, y2, maxZ, 1.0f, 1.0f)
PRISM_VERT(x1, y1, minZ, 0.0f, 0.0f)
PRISM_VERT(x2, y2, maxZ, 1.0f, 1.0f)
PRISM_VERT(x1, y1, maxZ, 0.0f, 1.0f)
/* --- Side face 2: edge p2->p0 --- */
PRISM_VERT(x2, y2, minZ, 0.0f, 0.0f)
PRISM_VERT(x0, y0, minZ, 1.0f, 0.0f)
PRISM_VERT(x0, y0, maxZ, 1.0f, 1.0f)
PRISM_VERT(x2, y2, minZ, 0.0f, 0.0f)
PRISM_VERT(x0, y0, maxZ, 1.0f, 1.0f)
PRISM_VERT(x2, y2, maxZ, 0.0f, 1.0f)
#undef PRISM_VERT
}
@@ -1,50 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/mesh.h"
#include "display/color.h"
#define TRIPRISM_VERTEX_COUNT 24
#define TRIPRISM_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
extern mesh_t TRIPRISM_MESH_SIMPLE;
extern meshvertex_t TRIPRISM_MESH_SIMPLE_VERTICES[TRIPRISM_VERTEX_COUNT];
/**
* Initializes the simple unit triangular prism. The cross-section triangle has
* vertices (0,0), (1,0), (0.5,1) in XY, extruded from z=0 to z=1.
*
* @return Error for initialization of the triangular prism mesh.
*/
errorret_t triPrismInit();
/**
* Buffers a triangular prism into the provided vertex array.
* The triangular cross-section is defined by three 2D points in the XY plane;
* the prism is extruded along the Z axis between minZ and maxZ.
* Writes TRIPRISM_VERTEX_COUNT (24) vertices (CCW winding).
*
* @param vertices Vertex array to write into.
* @param x0,y0 First triangle vertex (XY).
* @param x1,y1 Second triangle vertex (XY).
* @param x2,y2 Third triangle vertex (XY).
* @param minZ Near Z extent of the prism.
* @param maxZ Far Z extent of the prism.
* @param color Color applied to all vertices.
*/
void triPrismBuffer(
meshvertex_t *vertices,
const float_t x0, const float_t y0,
const float_t x1, const float_t y1,
const float_t x2, const float_t y2,
const float_t minZ,
const float_t maxZ
#if MESH_ENABLE_COLOR
, const color_t color
#endif
);
@@ -1,12 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
shader.c
shaderlist.c
shaderunlit.c
)
@@ -1,87 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "shaderlist.h"
#include "display/screen/screen.h"
#include "assert/assert.h"
shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT] = {
[SHADER_LIST_SHADER_UNLIT] = {
.shader = &SHADER_UNLIT,
.definition = &SHADER_UNLIT_DEFINITION
},
};
errorret_t shaderListInit() {
mat4 view, proj, model;
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
glm_perspective(
glm_rad(45.0f),
SCREEN.aspect,
0.1f,
100.0f,
proj
);
glm_mat4_identity(model);
for(shaderlistshader_t i = 0; i < SHADER_LIST_SHADER_COUNT; i++) {
if(i == SHADER_LIST_SHADER_NULL) {
continue;
}
assertNotNull(
SHADER_LIST_DEFS[i].shader, "Shader cannot be null"
);
assertNotNull(
SHADER_LIST_DEFS[i].definition, "Shader definition cannot be null"
);
errorChain(shaderInit(
SHADER_LIST_DEFS[i].shader, SHADER_LIST_DEFS[i].definition
));
errorChain(shaderBind(SHADER_LIST_DEFS[i].shader));
errorChain(shaderSetMatrix(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_PROJECTION, proj
));
errorChain(shaderSetMatrix(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_VIEW, view
));
errorChain(shaderSetMatrix(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_MODEL, model
));
errorChain(shaderSetTexture(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_TEXTURE, NULL
));
errorChain(shaderSetColor(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_COLOR, COLOR_WHITE
));
}
errorOk();
}
errorret_t shaderListDispose(void) {
for(shaderlistshader_t i = 0; i < SHADER_LIST_SHADER_COUNT; i++) {
if(i == SHADER_LIST_SHADER_NULL) {
continue;
}
assertNotNull(
SHADER_LIST_DEFS[i].shader, "Shader cannot be null"
);
errorChain(shaderDispose(SHADER_LIST_DEFS[i].shader));
}
errorOk();
}
@@ -1,40 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/shader/shader.h"
#include "display/shader/shaderunlit.h"
typedef enum {
SHADER_LIST_SHADER_NULL,
SHADER_LIST_SHADER_UNLIT,
SHADER_LIST_SHADER_COUNT
} shaderlistshader_t;
typedef struct {
shader_t *shader;
shaderdefinition_t *definition;
} shaderlistdef_t;
extern shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT];
/**
* Initializes all default shaders and uploads the initial view, projection,
* and model matrices to each.
*
* @return Error state.
*/
errorret_t shaderListInit();
/**
* Disposes all default shaders.
*
* @return Error state.
*/
errorret_t shaderListDispose(void);
@@ -1,13 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/shader/shaderlist.h"
typedef union shadermaterial_u {
shaderunlitmaterial_t unlit;
} shadermaterial_t;
@@ -1,32 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "shaderunlit.h"
#include "display/shader/shadermaterial.h"
shader_t SHADER_UNLIT = {
.definition = &SHADER_UNLIT_DEFINITION
};
errorret_t shaderUnlitSetMaterial(
shader_t *shader,
const shadermaterial_t *material
) {
errorChain(shaderSetTexture(
shader,
SHADER_UNLIT_TEXTURE,
material->unlit.texture
));
errorChain(shaderSetColor(
shader,
SHADER_UNLIT_COLOR,
material->unlit.color
));
errorOk();
}
@@ -1,35 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "shader.h"
#define SHADER_UNLIT_PROJECTION "u_Proj"
#define SHADER_UNLIT_VIEW "u_View"
#define SHADER_UNLIT_MODEL "u_Model"
#define SHADER_UNLIT_TEXTURE "u_Texture"
#define SHADER_UNLIT_COLOR "u_Color"
typedef struct {
color_t color;
texture_t *texture;
} shaderunlitmaterial_t;
extern shaderdefinition_t SHADER_UNLIT_DEFINITION;
extern shader_t SHADER_UNLIT;
/**
* Uploads the unlit material properties to the shader.
*
* @param shader The shader to upload to.
* @param material The material data to upload.
* @return Error if failure, otherwise errorOk.
*/
errorret_t shaderUnlitSetMaterial(
shader_t *shader,
const shadermaterial_t *material
);
@@ -1,179 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "spritebatch.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
#include "display/shader/shadermaterial.h"
meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
spritebatch_t SPRITEBATCH;
errorret_t spriteBatchInit() {
memoryZero(&SPRITEBATCH, sizeof(spritebatch_t));
errorChain(meshInit(
&SPRITEBATCH.mesh,
QUAD_PRIMITIVE_TYPE,
SPRITEBATCH_VERTEX_COUNT,
SPRITEBATCH_VERTICES
));
errorOk();
}
errorret_t spriteBatchBuffer(
const spritebatchsprite_t *sprites,
const uint32_t count,
shader_t *shader,
const shadermaterial_t material
) {
assertNotNull(sprites, "Sprites cannot be null");
assertTrue(count > 0, "Count must be greater than zero");
assertNotNull(shader, "Shader cannot be null");
// Did the shader or material data change?
if(shader != SPRITEBATCH.shader) {
errorChain(spriteBatchFlush());
SPRITEBATCH.shader = shader;
SPRITEBATCH.material = material;
} else if(memoryCompare(
&material, &SPRITEBATCH.material, sizeof(shadermaterial_t)
) != 0) {
// Did the material data change?
errorChain(spriteBatchFlush());
SPRITEBATCH.shader = shader;
SPRITEBATCH.material = material;
}
// Buffer the vertices.
uint32_t remaining = count;
do {
uint32_t spritesBeforeFlush = (
SPRITEBATCH_SPRITES_MAX_PER_FLUSH - SPRITEBATCH.spriteCount
);
if(spritesBeforeFlush == 0) {
// Flush if we have no capacity before flushing.
errorChain(spriteBatchFlush());
spritesBeforeFlush = SPRITEBATCH_SPRITES_MAX_PER_FLUSH;
}
// Many we buffering?
const uint32_t batchCount = mathMin(
remaining,
spritesBeforeFlush
);
// Destination
meshvertex_t *v = &SPRITEBATCH_VERTICES[
(SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush *
SPRITEBATCH_SPRITES_MAX_PER_FLUSH)) * QUAD_VERTEX_COUNT
];
// Buffer to the mesh vertices.
spriteBatchBufferToMesh(
sprites, batchCount, v, batchCount * QUAD_VERTEX_COUNT
);
SPRITEBATCH.spriteCount += batchCount;
remaining -= batchCount;
} while(remaining > 0);
errorOk();
}
void spriteBatchBufferToMesh(
const spritebatchsprite_t *sprites,
const uint32_t count,
meshvertex_t *vertices,
const uint32_t verticesSize
) {
assertNotNull(sprites, "Sprites cannot be null");
assertTrue(count > 0, "Count must be greater than zero");
assertNotNull(vertices, "Vertices cannot be null");
assertTrue(
verticesSize >= count * QUAD_VERTEX_COUNT, "Vertices array too small"
);
for(uint32_t i = 0; i < count; i++ ){
spritebatchsprite_t sprite = sprites[i];
meshvertex_t *v = &vertices[i * QUAD_VERTEX_COUNT];
// Buffer the quad
v[0].pos[0] = sprite.min[0];
v[0].pos[1] = sprite.min[1];
v[0].pos[2] = sprite.min[2];
v[0].uv[0] = sprite.uvMin[0];
v[0].uv[1] = sprite.uvMin[1];
v[1].pos[0] = sprite.max[0];
v[1].pos[1] = sprite.min[1];
v[1].pos[2] = sprite.min[2];
v[1].uv[0] = sprite.uvMax[0];
v[1].uv[1] = sprite.uvMin[1];
v[2].pos[0] = sprite.max[0];
v[2].pos[1] = sprite.max[1];
v[2].pos[2] = sprite.max[2];
v[2].uv[0] = sprite.uvMax[0];
v[2].uv[1] = sprite.uvMax[1];
v[3].pos[0] = sprite.min[0];
v[3].pos[1] = sprite.min[1];
v[3].pos[2] = sprite.min[2];
v[3].uv[0] = sprite.uvMin[0];
v[3].uv[1] = sprite.uvMin[1];
v[4].pos[0] = sprite.max[0];
v[4].pos[1] = sprite.max[1];
v[4].pos[2] = sprite.max[2];
v[4].uv[0] = sprite.uvMax[0];
v[4].uv[1] = sprite.uvMax[1];
v[5].pos[0] = sprite.min[0];
v[5].pos[1] = sprite.max[1];
v[5].pos[2] = sprite.max[2];
v[5].uv[0] = sprite.uvMin[0];
v[5].uv[1] = sprite.uvMax[1];
}
}
void spriteBatchClear() {
SPRITEBATCH.spriteCount = 0;
SPRITEBATCH.spriteFlush = 0;
SPRITEBATCH.shader = NULL;
memoryZero(&SPRITEBATCH.material, sizeof(shadermaterial_t));
}
errorret_t spriteBatchFlush() {
if(SPRITEBATCH.spriteCount == 0) {
errorOk();
}
size_t vertexCount = QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount;
size_t vertexOffset = (
SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH *
QUAD_VERTEX_COUNT
);
errorChain(shaderBind(SPRITEBATCH.shader));
errorChain(shaderSetMaterial(SPRITEBATCH.shader, &SPRITEBATCH.material));
errorChain(meshFlush(&SPRITEBATCH.mesh, vertexOffset, vertexCount));
errorChain(meshDraw(&SPRITEBATCH.mesh, vertexOffset, vertexCount));
SPRITEBATCH.spriteFlush++;
if(SPRITEBATCH.spriteFlush >= SPRITEBATCH_FLUSH_COUNT) {
SPRITEBATCH.spriteFlush = 0;
}
SPRITEBATCH.spriteCount = 0;
errorOk();
}
errorret_t spriteBatchDispose() {
errorChain(meshDispose(&SPRITEBATCH.mesh));
errorOk();
}
@@ -1,107 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/mesh/quad.h"
#include "display/texture/texture.h"
#include "display/shader/shadermaterial.h"
#define SPRITEBATCH_SPRITES_MAX 512
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
#define SPRITEBATCH_FLUSH_COUNT 16
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
)
typedef struct {
vec3 min;
vec3 max;
vec2 uvMin;
vec2 uvMax;
} spritebatchsprite_t;
typedef struct {
mesh_t mesh;
int32_t spriteCount;
int32_t spriteFlush;
shader_t *shader;
shadermaterial_t material;
} spritebatch_t;
// Have to define these separately because of alignment on certain platforms.
extern meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
extern spritebatch_t SPRITEBATCH;
/**
* Initializes the global sprite batch and its internal mesh buffer.
*
* @return Error state.
*/
errorret_t spriteBatchInit();
/**
* Lowest-level buffer function. Writes sprites into the internal vertex buffer.
* Flushes automatically when the per-flush capacity is reached. Does not
* modify material state - call spriteBatchSetState or use a high-level push
* function before buffering.
*
* @param sprites Pointer to the sprite array.
* @param count Number of sprites to buffer.
* @param shader Shader to use when flushing.
* @param material Material information passed to the shader when flushing.
* @return Error state.
*/
errorret_t spriteBatchBuffer(
const spritebatchsprite_t *sprites,
const uint32_t count,
shader_t *shader,
const shadermaterial_t material
);
/**
* Buffers an array of sprites to a given array of mesh vertices. This is the
* internal method that is used to buffer to the internal spritebatch mesh, but
* you can use it to achieve sprite buffering to a mesh you own.
*
* verticesSize is the size of the vertices array, we use this to ensure no
* buffer overflows.
*
* @param sprites Pointer to the sprite array.
* @param count Number of sprites to buffer.
* @param vertices Pointer to the vertex array to write to.
* @param verticesSize Size of the vertex array, in number of vertices.
*/
void spriteBatchBufferToMesh(
const spritebatchsprite_t *sprites,
const uint32_t count,
meshvertex_t *vertices,
const uint32_t verticesSize
);
/**
* Resets sprite and flush counters and clears the current material state.
* Calling spriteBatchFlush after this renders nothing.
*/
void spriteBatchClear();
/**
* Uploads and draws all buffered sprites. If a material type has been set via
* spriteBatchSetState or spriteBatchCheckState, the shader is bound and the
* material is applied first. If matType is NULL the caller is responsible for
* having the correct shader already bound. Does nothing if the buffer is empty.
*
* @return Error state.
*/
errorret_t spriteBatchFlush();
/**
* Disposes of the sprite batch and frees its internal mesh buffer.
*
* @return Error state.
*/
errorret_t spriteBatchDispose();
@@ -1,15 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/texture/texture.h"
#include "display/texture/tileset.h"
typedef struct {
texture_t *texture;
tileset_t *tileset;
} font_t;
@@ -1,148 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "text.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "display/spritebatch/spritebatch.h"
#include "asset/asset.h"
#include "asset/loader/display/assettextureloader.h"
#include "asset/loader/display/assettilesetloader.h"
#include "display/shader/shaderunlit.h"
font_t FONT_DEFAULT;
errorret_t textInit(void) {
assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA };
assetentry_t *entryTexture = assetLock(
"ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input
);
assetentry_t *entryTileset = assetLock(
"ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL
);
errorChain(assetRequireLoaded(entryTexture));
errorChain(assetRequireLoaded(entryTileset));
FONT_DEFAULT.texture = &entryTexture->data.texture;
FONT_DEFAULT.tileset = &entryTileset->data.tileset;
errorOk();
}
errorret_t textDispose(void) {
FONT_DEFAULT.texture = NULL;
FONT_DEFAULT.tileset = NULL;
assetUnlock("ui/minogram.png");
assetUnlock("ui/minogram.dtf");
errorOk();
}
spritebatchsprite_t textGetSprite(
const vec2 pos, const char_t c, const font_t *font
) {
assertNotNull(font, "Font cannot be NULL");
// Change char from ASCII to a tile index.
int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START;
if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) {
tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
}
assertTrue(
tileIndex >= 0 && tileIndex <= font->tileset->tileCount,
"Character is out of bounds for font tiles"
);
// Create sprite.
vec4 uv;
tilesetTileGetUV(font->tileset, tileIndex, uv);
spritebatchsprite_t sprite;
sprite.min[0] = pos[0];
sprite.min[1] = pos[1];
sprite.min[2] = 0.0f;
sprite.max[0] = pos[0] + font->tileset->tileWidth;
sprite.max[1] = pos[1] + font->tileset->tileHeight;
sprite.max[2] = 0.0f;
sprite.uvMin[0] = uv[0];
sprite.uvMin[1] = uv[1];
sprite.uvMax[0] = uv[2];
sprite.uvMax[1] = uv[3];
return sprite;
}
errorret_t textDraw(
const float_t x,
const float_t y,
const char_t *text,
const color_t color,
font_t *font
) {
assertNotNull(text, "Text cannot be NULL");
spritebatchsprite_t sprite;
shadermaterial_t material = {
.unlit = {
.color = color,
.texture = font->texture
}
};
float_t posX = x;
float_t posY = y;
char_t c;
int32_t i = 0;
while((c = text[i++]) != '\0') {
if(c == '\n') {
posX = x;
posY += font->tileset->tileHeight;
continue;
}
if(c == ' ') {
posX += font->tileset->tileWidth;
continue;
}
sprite = textGetSprite((vec2){posX, posY}, c, font);
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
posX += font->tileset->tileWidth;
}
errorOk();
}
void textMeasure(
const char_t *text,
const font_t *font,
int32_t *outWidth,
int32_t *outHeight
) {
assertNotNull(text, "Text cannot be NULL");
assertNotNull(outWidth, "Output width pointer cannot be NULL");
assertNotNull(outHeight, "Output height pointer cannot be NULL");
int32_t width = 0;
int32_t height = font->tileset->tileHeight;
int32_t lineWidth = 0;
char_t c;
int32_t i = 0;
while((c = text[i++]) != '\0') {
if(c == '\n') {
if(lineWidth > width) width = lineWidth;
lineWidth = 0;
height += font->tileset->tileHeight;
continue;
}
lineWidth += font->tileset->tileWidth;
}
if(lineWidth > width) width = lineWidth;
*outWidth = width;
*outHeight = height;
}
@@ -8,4 +8,6 @@
#pragma once
#include "dusk.h"
typedef void (*cutscenecallback_t)(void);
typedef struct {
void *nothing;
} inventory_t;
+1
View File
@@ -16,6 +16,7 @@ typedef enum {
typedef struct {
rpgcameramode_t mode;
union {
worldpos_t free;
struct {
-21
View File
@@ -1,21 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
const platformNames = {
[System.PLATFORM_LINUX]: 'Linux',
[System.PLATFORM_KNULLI]: 'Knulli',
[System.PLATFORM_PSP]: 'PSP',
[System.PLATFORM_GAMECUBE]: 'GameCube',
[System.PLATFORM_WII]: 'Wii',
};
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
UIFullboxOver.setColor(Color.BLACK);
requireAsync('testscene.js').then(Scene.set).catch(err => {
Console.print('Error loading scene: ' + err);
Engine.exit();
});
+77
View File
@@ -0,0 +1,77 @@
module('input')
module('platform')
module('scene')
module('locale')
-- Default Input bindings.
if PSP then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("circle", INPUT_ACTION_CANCEL)
inputBind("cross", INPUT_ACTION_ACCEPT)
inputBind("select", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif DOLPHIN then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("b", INPUT_ACTION_CANCEL)
inputBind("a", INPUT_ACTION_ACCEPT)
inputBind("z", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif LINUX then
if INPUT_KEYBOARD then
inputBind("w", INPUT_ACTION_UP)
inputBind("s", INPUT_ACTION_DOWN)
inputBind("a", INPUT_ACTION_LEFT)
inputBind("d", INPUT_ACTION_RIGHT)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("enter", INPUT_ACTION_ACCEPT)
inputBind("e", INPUT_ACTION_ACCEPT)
inputBind("q", INPUT_ACTION_CANCEL)
inputBind("escape", INPUT_ACTION_RAGEQUIT)
end
if INPUT_GAMEPAD then
inputBind("gamepad_up", INPUT_ACTION_UP)
inputBind("gamepad_down", INPUT_ACTION_DOWN)
inputBind("gamepad_left", INPUT_ACTION_LEFT)
inputBind("gamepad_right", INPUT_ACTION_RIGHT)
inputBind("gamepad_a", INPUT_ACTION_ACCEPT)
inputBind("gamepad_b", INPUT_ACTION_CANCEL)
inputBind("gamepad_back", INPUT_ACTION_RAGEQUIT)
inputBind("gamepad_lstick_up", INPUT_ACTION_UP)
inputBind("gamepad_lstick_down", INPUT_ACTION_DOWN)
inputBind("gamepad_lstick_left", INPUT_ACTION_LEFT)
inputBind("gamepad_lstick_right", INPUT_ACTION_RIGHT)
end
if INPUT_POINTER then
inputBind("mouse_x", INPUT_ACTION_POINTERX)
inputBind("mouse_y", INPUT_ACTION_POINTERY)
end
else
print("Unknown platform, no default input bindings set.")
end
sceneSet('scene/minesweeper.lua')
+5 -56
View File
@@ -1,60 +1,9 @@
#
msgid ""
msgstr ""
"Project-Id-Version: ExampleApp 1.0\n"
"Language: en\n"
"Language: en_US\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n<7 ? 2 : 3));\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: ui/menu.c:10
msgid "ui.title"
msgstr ""
"Welcome"
#: ui/user.c:22
msgid "ui.greeting"
msgstr "Hello, %s!"
#: ui/files.c:40
msgid "ui.file_status"
msgstr "%s has %d files."
#: ui/cart.c:55
msgid "cart.item_count"
msgid_plural "cart.item_count"
msgstr[0] "%d item"
msgstr[1] "%d items (dual)"
msgstr[2] "%d items (few)"
msgstr[3] "%d items (many)"
#: ui/notifications.c:71
msgid ""
"ui.multiline_help"
msgstr ""
"Line one of the help text.\n"
"Line two continues here.\n"
"Line three ends here."
#: ui/errors.c:90
msgid ""
"error.upload_failed.long"
msgstr ""
"Upload failed for file \"%s\".\n"
"Please try again later or contact support."
#: ui/messages.c:110
msgid ""
"user.invite_status"
msgid_plural ""
"user.invite_status"
msgstr[0] ""
"%s invited %d user.\n"
"Please review the request."
msgstr[1] ""
"%s invited %d users (dual).\n"
"Please review the requests."
msgstr[2] ""
"%s invited %d users (few).\n"
"Please review the requests."
msgstr[3] ""
"%s invited %d users (many).\n"
"Please review the requests."
msgid "ui.test"
msgstr "Hello this is a test."
Binary file not shown.
-63
View File
@@ -1,63 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
const PLAYER_SPEED = 5.0;
// 1 world unit = 16 pixels.
const PIXEL_SCALE = 1.0 / 16.0;
// Player sprite is 32x32 px (test.png dimensions).
const PLAYER_W = 32 * PIXEL_SCALE;
const PLAYER_H = 32 * PIXEL_SCALE;
var player = {};
player.getAssets = () => {
return [
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
];
}
player.init = function(scene) {
var texture = scene.assets.getAssetByPath('test.png');
Console.print('Player init: got texture ' + texture);
_entity = Entity.create();
_position = _entity.add(Component.POSITION);
_physics = _entity.add(Component.PHYSICS);
_physics.bodyType = Physics.DYNAMIC;
_physics.shape = Physics.SHAPE_CUBE;
_physics.gravityScale = 1.0;
var r = _entity.add(Component.RENDERABLE);
r.texture = texture.texture;
r.type = Renderable.SPRITEBATCH;
r.color = new Color(220, 80, 80);
// Upright quad centered on X, bottom-aligned on Y.
r.sprites = [[-PLAYER_W/2, 0, 0, PLAYER_W/2, PLAYER_H, 0, 0, 1, 1, 0]];
_position.localPosition = new Vec3(0, PLAYER_H, 0);
};
player.getPosition = function() {
return _position;
};
player.update = function() {
if(!_physics) return;
var vx = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT) * PLAYER_SPEED;
var vz = Input.axis(INPUT_ACTION_DOWN, INPUT_ACTION_UP) * PLAYER_SPEED;
// Preserve vertical velocity so gravity and landing work correctly.
var vy = _physics.velocity.y;
_physics.velocity = new Vec3(vx, vy, vz);
};
player.dispose = function() {
Entity.dispose(_entity);
_entity = null;
_position = null;
_physics = null;
};
module.exports = player;
+256
View File
@@ -0,0 +1,256 @@
module('spritebatch')
module('camera')
module('color')
module('ui')
module('screen')
module('time')
module('glm')
module('text')
module('tileset')
module('texture')
module('input')
CELL_STATE_DEFAULT = 0
CELL_STATE_HOVER = 1
CELL_STATE_DOWN = 2
CELL_STATE_DISABLED = 3
screenSetBackground(colorCornflowerBlue())
camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC)
-- tilesetUi = tilesetGetByName("ui")
-- textureUi = textureLoad(tilesetUi.texture)
-- tilesetBorder = tilesetGetByName("border")
-- textureBorder = textureLoad(tilesetBorder.texture)
-- textureGrid = textureLoad("minesweeper/grid_bg.dpi")
-- tilesetCell = tilesetGetByName("cell")
-- textureCell = textureLoad(tilesetCell.texture)
-- cellSliceDefault = tilesetPositionGetUV(tilesetCell, 3, 5)
-- cellSliceHover = tilesetPositionGetUV(tilesetCell, 3, 4)
-- cellSliceDown = tilesetPositionGetUV(tilesetCell, 3, 6)
-- cellSliceDisabled = tilesetPositionGetUV(tilesetCell, 3, 7)
-- sweepwerCols = 10
-- sweeperRows = 14
-- mouseX = -1
-- mouseY = -1
-- centerX = 0
-- centerY = 0
-- boardWidth = sweepwerCols * tilesetCell.tileWidth
-- boardHeight = sweeperRows * tilesetCell.tileHeight
-- i = 0
-- cells = {}
-- for y = 1, sweeperRows do
-- for x = 1, sweepwerCols do
-- cells[i] = CELL_STATE_DEFAULT
-- i = i + 1
-- end
-- end
function cellDraw(x, y, type)
local slice = cellSliceDefault
if type == CELL_STATE_HOVER then
slice = cellSliceHover
elseif type == CELL_STATE_DOWN then
slice = cellSliceDown
elseif type == CELL_STATE_DISABLED then
slice = cellSliceDisabled
end
spriteBatchPush(textureCell,
x, y,
x + tilesetCell.tileWidth, y + tilesetCell.tileHeight,
colorWhite(),
slice.u0, slice.v0,
slice.u1, slice.v1
)
end
function backgroundDraw()
local t = (TIME.time / 40) % 1
local scaleX = screenGetWidth() / textureGrid.width
local scaleY = screenGetHeight() / textureGrid.height
local u0 = t * scaleX
local v0 = t * scaleY
local u1 = scaleX + u0
local v1 = scaleY + v0
spriteBatchPush(textureGrid,
0, 0,
screenGetWidth(), screenGetHeight(),
colorWhite(),
u0, v0,
u1, v1
)
end
function borderDraw(x, y, innerWidth, innerHeight)
-- Top Left
local uv = tilesetPositionGetUV(tilesetBorder, 0, 0)
spriteBatchPush(textureBorder,
x - tilesetBorder.tileWidth, y - tilesetBorder.tileWidth,
x, y,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Top Right
uv = tilesetPositionGetUV(tilesetBorder, 10, 0)
spriteBatchPush(textureBorder,
x + innerWidth, y - tilesetBorder.tileHeight,
x + innerWidth + tilesetBorder.tileWidth, y,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Bottom Left
uv = tilesetPositionGetUV(tilesetBorder, 0, 10)
spriteBatchPush(textureBorder,
x - tilesetBorder.tileWidth, y + innerHeight,
x, y + innerHeight + tilesetBorder.tileHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Bottom Right
uv = tilesetPositionGetUV(tilesetBorder, 10, 10)
spriteBatchPush(textureBorder,
x + innerWidth, y + innerHeight,
x + innerWidth + tilesetBorder.tileWidth, y + innerHeight + tilesetBorder.tileHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Top
uv = tilesetPositionGetUV(tilesetBorder, 1, 0)
spriteBatchPush(textureBorder,
x, y - tilesetBorder.tileHeight,
x + innerWidth, y,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Bottom
uv = tilesetPositionGetUV(tilesetBorder, 1, 10)
spriteBatchPush(textureBorder,
x, y + innerHeight,
x + innerWidth, y + innerHeight + tilesetBorder.tileHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Left
uv = tilesetPositionGetUV(tilesetBorder, 0, 1)
spriteBatchPush(textureBorder,
x - tilesetBorder.tileWidth, y,
x, y + innerHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Right
uv = tilesetPositionGetUV(tilesetBorder, 10, 1)
spriteBatchPush(textureBorder,
x + innerWidth, y,
x + innerWidth + tilesetBorder.tileWidth, y + innerHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
end
x = 0
y = 0
function sceneDispose()
end
function sceneUpdate()
x = x + inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT)
y = y + inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN)
end
function sceneRender()
-- Update camera
cameraPushMatrix(camera)
camera.bottom = screenGetHeight()
camera.right = screenGetWidth()
spriteBatchPush(
nil,
x, y, x + 32, y + 32,
colorWhite()
)
-- Update mouse position
-- if INPUT_POINTER then
-- mouseX = inputGetValue(INPUT_ACTION_POINTERX) * screenGetWidth()
-- mouseY = inputGetValue(INPUT_ACTION_POINTERY) * screenGetHeight()
-- -- Draw cursor
-- spriteBatchPush(
-- nil,
-- mouseX - 2, mouseY - 2,
-- mouseX + 2, mouseY + 2,
-- colorRed(),
-- 0, 0,
-- 1, 1
-- )
-- end
-- textDraw(10, 10, "Hello World")
-- centerX = math.floor(screenGetWidth() / 2)
-- centerY = math.floor(screenGetHeight() / 2)
-- Draw elements
-- backgroundDraw()
-- borderDraw(
-- centerX - (boardWidth / 2), centerY - (boardHeight / 2),
-- boardWidth, boardHeight
-- )
-- i = 0
-- -- Foreach cell
-- local offX = centerX - (boardWidth / 2)
-- local offY = centerY - (boardHeight / 2)
-- for y = 0, sweeperRows - 1 do
-- for x = 0, sweepwerCols - 1 do
-- i = y * sweepwerCols + x
-- -- Hovered
-- if
-- cells[i] == CELL_STATE_DEFAULT and
-- mouseX >= x * tilesetCell.tileWidth + offX and mouseX < (x + 1) * tilesetCell.tileWidth + offX and
-- mouseY >= y * tilesetCell.tileHeight + offY and mouseY < (y + 1) * tilesetCell.tileHeight + offY
-- then
-- cells[i] = CELL_STATE_HOVER
-- else
-- cells[i] = CELL_STATE_DEFAULT
-- end
-- cellDraw(
-- x * tilesetCell.tileWidth + offX,
-- y * tilesetCell.tileHeight + offY,
-- cells[i]
-- )
-- end
-- end
spriteBatchFlush()
cameraPopMatrix()
end
-42
View File
@@ -1,42 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
var scene = {};
// Pokemon DS-style camera: ~34 degrees elevation (atan(6/9)).
// CAM_HEIGHT / CAM_DIST ratio controls the tilt - keep it under 0.7 for
// the characteristically shallow DS angle.
const CAM_HEIGHT = 6;
const CAM_DIST = 9;
scene.init = async function() {
// Camera
scene.cam = Entity.create();
var camPos = scene.cam.add(Component.POSITION);
var cam = scene.cam.add(Component.CAMERA);
camPos.localPosition = new Vec3(3, 3, 3);
camPos.lookAt(new Vec3(0, 0, 0));
// Floor - large flat slab, no texture needed.
scene.floor = Entity.create();
var floorPos = scene.floor.add(Component.POSITION);
var floorR = scene.floor.add(Component.RENDERABLE);
floorR.type = Renderable.SHADER_MATERIAL;
floorR.color = Color.BLUE;
// floorPos.localScale = new Vec3(16, 0.2, 16);
// floorPos.localPosition = new Vec3(0, -0.1, 0);
await UIFullboxOver.transition(Color.BLACK, Color.TRANSPARENT, 1.0);
};
scene.update = function() {
};
scene.dispose = function() {
Entity.dispose(scene.floor);
Entity.dispose(scene.cam);
};
module.exports = scene;
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

-6
View File
@@ -1,6 +0,0 @@
module = {
render() {
Text.draw(0, 0, "Hello World");
SpriteBatch.flush();
}
};
+17
View File
@@ -0,0 +1,17 @@
include(FetchContent)
set(ENABLE_ZSTD OFF CACHE BOOL "" FORCE)
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(BUILD_REGRESS OFF CACHE BOOL "" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_DOC OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(LIBZIP_DO_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
libzip
GIT_REPOSITORY https://github.com/nih-at/libzip.git
GIT_TAG v1.11.4
)
FetchContent_MakeAvailable(libzip)
+20
View File
@@ -0,0 +1,20 @@
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
+34 -8
View File
@@ -1,17 +1,43 @@
# Allow user to manually specify libzip paths
# LIBZIP_ROOT: root directory for libzip (optional)
# LIBZIP_INCLUDE_DIR: path to zip.h (optional)
# LIBZIP_LIBRARY: path to libzip library (optional)
find_package(ZLIB REQUIRED)
find_path(LIBZIP_INCLUDE_DIR NAMES zip.h)
if(NOT LIBZIP_INCLUDE_DIR AND LIBZIP_ROOT)
find_path(LIBZIP_INCLUDE_DIR NAMES zip.h HINTS "${LIBZIP_ROOT}/include")
endif()
if(NOT LIBZIP_INCLUDE_DIR)
find_path(LIBZIP_INCLUDE_DIR NAMES zip.h)
endif()
mark_as_advanced(LIBZIP_INCLUDE_DIR)
find_library(LIBZIP_LIBRARY NAMES zip)
if(NOT LIBZIP_LIBRARY AND LIBZIP_ROOT)
find_library(LIBZIP_LIBRARY NAMES zip HINTS "${LIBZIP_ROOT}/lib")
endif()
if(NOT LIBZIP_LIBRARY)
find_library(LIBZIP_LIBRARY NAMES zip)
endif()
mark_as_advanced(LIBZIP_LIBRARY)
get_filename_component(_libzip_libdir ${LIBZIP_LIBRARY} DIRECTORY)
find_file(_libzip_pkgcfg libzip.pc
HINTS ${_libzip_libdir} ${LIBZIP_INCLUDE_DIR}/..
PATH_SUFFIXES pkgconfig lib/pkgconfig libdata/pkgconfig
NO_DEFAULT_PATH
)
if(LIBZIP_LIBRARY)
get_filename_component(_libzip_libdir ${LIBZIP_LIBRARY} DIRECTORY)
endif()
if(NOT _libzip_pkgcfg AND LIBZIP_ROOT)
find_file(_libzip_pkgcfg libzip.pc
HINTS "${LIBZIP_ROOT}/lib/pkgconfig" "${LIBZIP_ROOT}/libdata/pkgconfig"
NO_DEFAULT_PATH
)
endif()
if(NOT _libzip_pkgcfg AND LIBZIP_LIBRARY)
find_file(_libzip_pkgcfg libzip.pc
HINTS ${_libzip_libdir} ${LIBZIP_INCLUDE_DIR}/..
PATH_SUFFIXES pkgconfig lib/pkgconfig libdata/pkgconfig
NO_DEFAULT_PATH
)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
-31
View File
@@ -1,31 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(FetchContent)
FetchContent_Declare(
stb
GIT_REPOSITORY https://github.com/nothings/stb.git
)
# Fetch stb if not already done
FetchContent_MakeAvailable(stb)
# Find the stb_image.h header
set(STB_INCLUDE_DIR "${stb_SOURCE_DIR}")
set(STB_IMAGE_HEADER "${stb_SOURCE_DIR}/stb_image.h")
if(EXISTS "${STB_IMAGE_HEADER}")
add_library(stb_image INTERFACE)
target_include_directories(stb_image INTERFACE "${STB_INCLUDE_DIR}")
set(STB_IMAGE_FOUND TRUE)
else()
set(STB_IMAGE_FOUND FALSE)
endif()
# Standard CMake variables
set(STB_IMAGE_INCLUDE_DIRS "${STB_INCLUDE_DIR}")
set(STB_IMAGE_LIBRARIES stb_image)
mark_as_advanced(STB_IMAGE_INCLUDE_DIRS STB_IMAGE_LIBRARIES STB_IMAGE_FOUND)
-18
View File
@@ -1,18 +0,0 @@
include(FetchContent)
if(NOT TARGET yyjson)
FetchContent_Declare(
yyjson
GIT_REPOSITORY https://github.com/ibireme/yyjson.git
GIT_TAG 0.12.0
)
FetchContent_MakeAvailable(yyjson)
endif()
# Provide an imported target if not already available
if(NOT TARGET yyjson::yyjson)
add_library(yyjson::yyjson ALIAS yyjson)
endif()
# Mark yyjson as found for find_package compatibility
set(yyjson_FOUND TRUE)
+14 -45
View File
@@ -1,69 +1,38 @@
# Build type: DOL (SD/USB via libfat) or ISO (DVD disc via libogc DVD driver)
set(DUSK_DOLPHIN_BUILD_TYPE "DOL" CACHE STRING "Dolphin asset source: DOL (SD/USB) or ISO (DVD disc)")
set_property(CACHE DUSK_DOLPHIN_BUILD_TYPE PROPERTY STRINGS "DOL" "ISO")
# Numeric tokens so #if DUSK_DOLPHIN_BUILD_TYPE == DOL works in C.
# DUSK_DOLPHIN_BUILD_TYPE is passed without quotes so it expands to the identifier.
# Target definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_DOLPHIN
DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480
DUSK_THREAD_PTHREAD
DOL=1
ISO=2
DUSK_DOLPHIN_BUILD_TYPE=${DUSK_DOLPHIN_BUILD_TYPE}
)
# Custom compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
# Need PkgConfig
find_package(PkgConfig REQUIRED)
pkg_check_modules(zip IMPORTED_TARGET libzip)
# Disable all warnings
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE -w)
# cglm: fetched at source level via Findcglm.cmake (FetchContent, headers only)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Pre-create ZLIB::ZLIB so any downstream cmake module that resolves it
# (FindZLIB, pkg-config IMPORTED_TARGET Requires processing) gets plain -lz
# rather than an unresolvable IMPORTED target that the PPC linker rejects.
if(NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB INTERFACE IMPORTED GLOBAL)
set_target_properties(ZLIB::ZLIB PROPERTIES INTERFACE_LINK_LIBRARIES "z")
endif()
include(cmake/modules/CompileLua.cmake)
# Mark libzip as found so src/dusk/CMakeLists.txt skips Findlibzip.cmake.
# Findlibzip.cmake calls find_package(ZLIB) which can recreate a broken
# ZLIB::ZLIB IMPORTED target, bypassing the shim above.
set(libzip_FOUND TRUE CACHE BOOL "libzip found (devkitpro portlibs)" FORCE)
# Locate zip.h in the devkitpro sysroot (respects CMAKE_FIND_ROOT_PATH).
find_path(_dusk_zip_inc NAMES zip.h)
if(_dusk_zip_inc)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE "${_dusk_zip_inc}")
endif()
# Link libraries.
# zip/z/lzma use target_link_options (raw flags) to bypass cmake target
# resolution — pkg-config-generated targets for these carry ZLIB::ZLIB in
# INTERFACE_LINK_LIBRARIES which breaks the PPC link step.
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
m
zip
bz2
zstd
z
lzma
fat
PkgConfig::zip
)
if(DUSK_DOLPHIN_BUILD_TYPE STREQUAL "ISO")
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_DOLPHIN_BUILD_ISO)
else()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE fat)
endif()
# Postbuild: ELF -> DOL
# Postbuild
set(DUSK_BINARY_TARGET_NAME_DOL "${DUSK_BUILD_DIR}/Dusk.dol")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND elf2dol
-19
View File
@@ -3,22 +3,3 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_GAMECUBE
)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
# bba
)
# ISO post-build: produce NTSC-J, NTSC-U and PAL disc images
if(DUSK_DOLPHIN_BUILD_TYPE STREQUAL "ISO")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/tools/makedolphiniso.py"
"GCN"
"${DUSK_BINARY_TARGET_NAME_DOL}"
"${DUSK_ASSETS_ZIP}"
"${DUSK_GAME_NAME}"
"${DUSK_BUILD_DIR}"
COMMENT "Building GameCube ISO images (NTSC-J, NTSC-U, PAL)"
)
endif()
-1
View File
@@ -34,7 +34,6 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_OPENGL
DUSK_OPENGL_ES
DUSK_LINUX
DUSK_KNULLI
DUSK_DISPLAY_SIZE_DYNAMIC
DUSK_DISPLAY_WIDTH_DEFAULT=640
DUSK_DISPLAY_HEIGHT_DEFAULT=480
-7
View File
@@ -1,7 +1,6 @@
# Find link platform-specific libraries
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
# find_package(CURL REQUIRED)
# Setup endianess at compile time to optimize.
include(TestBigEndian)
@@ -23,16 +22,12 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
OpenGL::GL
GL
m
# CURL::libcurl
)
set(DUSK_BACKTRACE ON CACHE BOOL "Enable backtrace support for assert failures.")
# Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_CONSOLE_POSIX
# DUSK_OPENGL_LEGACY
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
@@ -43,6 +38,4 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD
)
+7 -20
View File
@@ -1,18 +1,10 @@
set(CMAKE_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_ARCHIVE_CREATE "$ENV{PSPDEV}/bin/psp-ar qc <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_APPEND "$ENV{PSPDEV}/bin/psp-ar q <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "$ENV{PSPDEV}/bin/psp-ranlib <TARGET>")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
find_package(SDL2 REQUIRED)
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
SDL2
pthread
OpenGL::GL
zip
bz2
z
@@ -26,32 +18,27 @@ target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
pspge
pspctrl
pspgu
pspgum
pspaudio
pspaudiolib
psputility
pspvfpu
pspvram
psphprm
pspnet
pspnet_inet
pspnet_apctl
psphttp
pspssl
)
target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_PSP
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD
)
# Postbuild, create .pbp file for PSP.
-106
View File
@@ -1,106 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Resolve YAUL_INSTALL_ROOT (already set by the toolchain file, but guard
# in case cmake/targets/ is loaded standalone for IDE tooling).
if(NOT DEFINED YAUL_INSTALL_ROOT)
if(DEFINED ENV{YAUL_INSTALL_ROOT})
set(YAUL_INSTALL_ROOT "$ENV{YAUL_INSTALL_ROOT}")
else()
set(YAUL_INSTALL_ROOT "/opt/yaul")
endif()
endif()
# Yaul installs headers/libs under the arch-prefix sysroot subdirectory:
# ${YAUL_INSTALL_ROOT}/sh2eb-elf/include/
# ${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/
# Cross-compiled zlib and libzip are installed to the same sysroot.
set(_YAUL_SYSROOT "${YAUL_INSTALL_ROOT}/sh2eb-elf")
set(_YAUL_BIN "${YAUL_INSTALL_ROOT}/bin")
# ---------------------------------------------------------------------------
# Bypass system find_package calls for libraries we cross-compile into the
# sh2eb-elf sysroot and install into ${_YAUL_SYSROOT}.
# ---------------------------------------------------------------------------
# zlib
if(NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB INTERFACE IMPORTED GLOBAL)
set_target_properties(ZLIB::ZLIB PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_YAUL_SYSROOT}/include"
INTERFACE_LINK_LIBRARIES "${_YAUL_SYSROOT}/lib/libz.a"
)
endif()
set(ZLIB_FOUND TRUE CACHE BOOL "" FORCE)
set(ZLIB_INCLUDE_DIR "${_YAUL_SYSROOT}/include" CACHE PATH "" FORCE)
set(ZLIB_LIBRARY "${_YAUL_SYSROOT}/lib/libz.a" CACHE FILEPATH "" FORCE)
# libzip — pre-installed into the sh2eb-elf sysroot; skip Findlibzip.cmake.
set(libzip_FOUND TRUE CACHE BOOL "libzip found (Saturn sysroot)" FORCE)
find_path(_sat_zip_inc NAMES zip.h
PATHS "${_YAUL_SYSROOT}/include"
NO_DEFAULT_PATH
)
if(_sat_zip_inc)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE "${_sat_zip_inc}")
endif()
# ---------------------------------------------------------------------------
# Compile definitions
# ---------------------------------------------------------------------------
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SATURN
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_BIG
DUSK_DISPLAY_WIDTH=320
DUSK_DISPLAY_HEIGHT=224
DUSK_THREAD_NONE
)
# ---------------------------------------------------------------------------
# Compile options
# ---------------------------------------------------------------------------
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
-m2 -mb
-fno-builtin
-fomit-frame-pointer
-w
)
# ---------------------------------------------------------------------------
# Include paths
# ---------------------------------------------------------------------------
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
"${_YAUL_SYSROOT}/include"
"${_YAUL_SYSROOT}/include/yaul"
)
# ---------------------------------------------------------------------------
# Link libraries
# ---------------------------------------------------------------------------
target_link_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
"${_YAUL_SYSROOT}/lib"
)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
"${_YAUL_SYSROOT}/lib/libyaul.a"
"${_YAUL_SYSROOT}/lib/libzip.a"
"${_YAUL_SYSROOT}/lib/libz.a"
m
)
# ---------------------------------------------------------------------------
# Post-build: ELF → binary image
# sh2eb-elf-objcopy converts the ELF to a flat binary that IP.BIN loads
# into Saturn Work RAM.
# ---------------------------------------------------------------------------
set(DUSK_SAT_BIN "${CMAKE_BINARY_DIR}/Dusk.bin")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND "${_YAUL_BIN}/sh2eb-elf-objcopy"
-O binary
"$<TARGET_FILE:${DUSK_BINARY_TARGET_NAME}>"
"${DUSK_SAT_BIN}"
COMMENT "Converting ${DUSK_BINARY_TARGET_NAME} ELF → ${DUSK_SAT_BIN}"
)
+17 -43
View File
@@ -1,41 +1,17 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include("${VITASDK}/share/vita.cmake" REQUIRED)
if(NOT DEFINED ENV{VITASDK})
message(FATAL_ERROR "VITASDK environment variable is not set.")
endif()
include("$ENV{VITASDK}/share/vita.cmake" REQUIRED)
# Manually define libzip for Vita
set(LIBZIP_LIBRARY "${VITASDK}/lib/libzip.a" CACHE FILEPATH "libzip library for Vita")
set(LIBZIP_INCLUDE_DIR "${VITASDK}/include" CACHE PATH "libzip include dir for Vita")
set(VITA_APP_NAME "Dusk")
set(VITA_TITLEID "DUSK00001")
set(VITA_VERSION "01.00")
set(VITA_APP_NAME "Red Rectangle")
set(VITA_TITLEID "VSDK00017")
set(VITA_VERSION "01.00")
find_package(SDL2 REQUIRED)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
cglm
SDL2
SDL2main
zip
bz2
z
zstd
crypto
lzma
m
pthread
stdc++
vitaGL
mathneon
vitashark
kubridge_stub
@@ -53,13 +29,12 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SceVshBridge_stub
SceIofilemgr_stub
SceShaccCgExt
SDL2-static
libtaihen_stub.a
# SceKernel_stub
SceAppUtil_stub
SceHid_stub
SceRtc_stub
lua::lua
zip
pthread
m
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
@@ -72,17 +47,16 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_VITA
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=960
DUSK_DISPLAY_HEIGHT=544
)
# Post-build: create SELF from the ELF binary (UNSAFE = homebrew, no signing)
vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME} UNSAFE)
# Post-build: package SELF + assets into a .vpk installable on the Vita
vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME})
vita_create_vpk(${DUSK_BINARY_TARGET_NAME}.vpk ${VITA_TITLEID} ${DUSK_BINARY_TARGET_NAME}.self
VERSION ${VITA_VERSION}
NAME ${VITA_APP_NAME}
FILE ${DUSK_ASSETS_ZIP} dusk.dsk
# FILE sce_sys/icon0.png sce_sys/icon0.png
# FILE sce_sys/livearea/contents/bg.png sce_sys/livearea/contents/bg.png
# FILE sce_sys/livearea/contents/startup.png sce_sys/livearea/contents/startup.png
# FILE sce_sys/livearea/contents/template.xml sce_sys/livearea/contents/template.xml
)
-22
View File
@@ -3,25 +3,3 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_WII
)
# Generate Homebrew Channel meta.xml from project identity variables
string(TIMESTAMP DUSK_BUILD_DATE "%Y%m%d000000" UTC)
configure_file(
"${CMAKE_SOURCE_DIR}/docker/dolphin/meta.xml.in"
"${DUSK_BUILD_DIR}/meta.xml"
@ONLY
)
# ISO post-build: produce NTSC-J, NTSC-U and PAL disc images
if(DUSK_DOLPHIN_BUILD_TYPE STREQUAL "ISO")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/tools/makedolphiniso.py"
"WII"
"${DUSK_BINARY_TARGET_NAME_DOL}"
"${DUSK_ASSETS_ZIP}"
"${DUSK_GAME_NAME}"
"${DUSK_BUILD_DIR}"
COMMENT "Building Wii ISO images (NTSC-J, NTSC-U, PAL)"
)
endif()
-59
View File
@@ -1,59 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
#
# CMake toolchain file for Sega Saturn (Hitachi SH-2, big-endian)
# using the Yaul homebrew SDK. Set YAUL_INSTALL_ROOT or the
# YAUL_INSTALL_ROOT environment variable before invoking cmake.
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR sh2)
# Resolve Yaul install root
if(NOT DEFINED YAUL_INSTALL_ROOT)
if(DEFINED ENV{YAUL_INSTALL_ROOT})
set(YAUL_INSTALL_ROOT "$ENV{YAUL_INSTALL_ROOT}"
CACHE PATH "Yaul SDK root" FORCE)
else()
set(YAUL_INSTALL_ROOT "/opt/yaul"
CACHE PATH "Yaul SDK root" FORCE)
endif()
endif()
# Yaul SH-2 cross-compiler prefix is sh2eb-elf (big-endian SH-2 ELF).
# Binaries live in ${YAUL_INSTALL_ROOT}/bin/; headers/libs under
# ${YAUL_INSTALL_ROOT}/sh2eb-elf/{include,lib}/.
set(_YAUL_BIN "${YAUL_INSTALL_ROOT}/bin")
set(CMAKE_C_COMPILER "${_YAUL_BIN}/sh2eb-elf-gcc")
set(CMAKE_CXX_COMPILER "${_YAUL_BIN}/sh2eb-elf-g++")
set(CMAKE_AR "${_YAUL_BIN}/sh2eb-elf-ar")
set(CMAKE_RANLIB "${_YAUL_BIN}/sh2eb-elf-ranlib")
set(CMAKE_STRIP "${_YAUL_BIN}/sh2eb-elf-strip")
set(CMAKE_OBJCOPY "${_YAUL_BIN}/sh2eb-elf-objcopy")
set(CMAKE_LINKER "${_YAUL_BIN}/sh2eb-elf-ld")
set(CMAKE_CROSSCOMPILING TRUE)
# Tell CMake not to try to run built executables
set(CMAKE_CROSSCOMPILING_EMULATOR "" CACHE STRING "" FORCE)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# Sysroot — Yaul installs headers/libs under the arch-prefix subdirectory
set(_YAUL_SYSROOT "${YAUL_INSTALL_ROOT}/sh2eb-elf")
set(CMAKE_FIND_ROOT_PATH "${_YAUL_SYSROOT}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# SH-2 core flags: big-endian (-mb), SH-2 ISA (-m2), no FPU
set(_SAT_C_FLAGS "-m2 -mb -fno-builtin -fomit-frame-pointer")
set(CMAKE_C_FLAGS_INIT "${_SAT_C_FLAGS}")
# Yaul provides its own startup code and linker script.
# The kernel.ld script maps Saturn Work RAM (0x06000000+).
set(_YAUL_LD "${YAUL_INSTALL_ROOT}/share/yaul/ip/kernel.ld")
set(CMAKE_EXE_LINKER_FLAGS_INIT
"-T\"${_YAUL_LD}\" -Wl,--start-group -Wl,--end-group -nostartfiles")
+3 -4
View File
@@ -1,7 +1,6 @@
FROM ghcr.io/extremscorner/libogc2
FROM devkitpro/devkitppc
WORKDIR /workdir
RUN apt update && \
dkp-pacman -Syu --noconfirm && \
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl xorriso && \
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip libogc2 gamecube-tools ppc-libmad ppc-zlib-ng ppc-liblzma ppc-bzip2 ppc-zstd
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl && \
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
VOLUME ["/workdir"]
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<app version="1">
<name>Dusk</name>
<version>1.00</version>
<release_date></release_date>
<coder>YouWish</coder>
<short_description>Dusk game</short_description>
<long_description>No description yet.</long_description>
<ahb_access/>
</app>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<app version="1">
<name>@DUSK_GAME_NAME@</name>
<version>@PROJECT_VERSION@</version>
<release_date>@DUSK_BUILD_DATE@</release_date>
<coder>@DUSK_GAME_AUTHOR@</coder>
<short_description>@DUSK_GAME_SHORT_DESCRIPTION@</short_description>
<long_description>@DUSK_GAME_LONG_DESCRIPTION@</long_description>
<ahb_access/>
</app>
+1 -1
View File
@@ -14,8 +14,8 @@ RUN apt-get install -y \
python3-dotenv \
python3-pyqt5 \
python3-opengl \
liblua5.3-dev \
xz-utils \
liblzma-dev \
libbz2-dev \
zlib1g-dev \
libzip-dev \
-248
View File
@@ -1,248 +0,0 @@
FROM ubuntu:22.04
LABEL org.opencontainers.image.description="Dusk Engine — Sega Saturn build environment (sh2eb-elf cross-compiler + Yaul SDK)"
ENV DEBIAN_FRONTEND=noninteractive
ENV YAUL_INSTALL_ROOT=/opt/yaul
# All variables required by Yaul's env.mk
ENV YAUL_ARCH_SH_PREFIX=sh2eb-elf
ENV YAUL_PROG_SH_PREFIX=sh2eb-elf
ENV YAUL_ARCH_M68K_PREFIX=m68keb-elf
ENV YAUL_BUILD_ROOT=/tmp/yaul-build
ENV YAUL_BUILD=build
ENV YAUL_OPTION_MALLOC_IMPL=tlsf
ENV PATH="${YAUL_INSTALL_ROOT}/bin:${PATH}"
# Toolchain source versions
ARG BINUTILS_VER=2.40
ARG GCC_VER=12.3.0
ARG NEWLIB_VER=4.3.0.20230120
# ---------------------------------------------------------------------------
# 1. Host build tools
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
git \
wget \
curl \
xz-utils \
python3 \
python3-pip \
python3-polib \
python3-pil \
python3-dotenv \
texinfo \
bison \
flex \
libgmp-dev \
libmpfr-dev \
libmpc-dev \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p "${YAUL_INSTALL_ROOT}"
# ---------------------------------------------------------------------------
# 2. Download cross-compiler sources
# ---------------------------------------------------------------------------
RUN cd /tmp && \
wget -q "https://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VER}.tar.xz" && \
wget -q "https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VER}/gcc-${GCC_VER}.tar.xz" && \
wget -q "ftp://sourceware.org/pub/newlib/newlib-${NEWLIB_VER}.tar.gz" && \
tar xf "binutils-${BINUTILS_VER}.tar.xz" && \
tar xf "gcc-${GCC_VER}.tar.xz" && \
tar xf "newlib-${NEWLIB_VER}.tar.gz" && \
rm "binutils-${BINUTILS_VER}.tar.xz" "gcc-${GCC_VER}.tar.xz" "newlib-${NEWLIB_VER}.tar.gz"
# Download GCC prerequisites (gmp, mpfr, mpc if not packaged)
RUN cd /tmp/gcc-${GCC_VER} && contrib/download_prerequisites
# ---------------------------------------------------------------------------
# 3. sh2eb-elf binutils (SH-2 big-endian)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-binutils && cd /tmp/build-sh-binutils && \
/tmp/binutils-${BINUTILS_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--disable-nls \
--disable-multilib \
--disable-werror \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-sh-binutils
# ---------------------------------------------------------------------------
# 4. sh2eb-elf GCC stage 1 (compiler only, no C library yet)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-gcc1 && cd /tmp/build-sh-gcc1 && \
/tmp/gcc-${GCC_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--enable-languages=c,c++ \
--without-headers \
--with-newlib \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-decimal-float \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
&& make -j"$(nproc)" all-gcc all-target-libgcc \
&& make install-gcc install-target-libgcc && \
rm -rf /tmp/build-sh-gcc1
# ---------------------------------------------------------------------------
# 5. newlib for sh2eb-elf (C runtime for embedded SH-2)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-newlib && cd /tmp/build-sh-newlib && \
/tmp/newlib-${NEWLIB_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--disable-newlib-supplied-syscalls \
--enable-newlib-reent-small \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-sh-newlib
# ---------------------------------------------------------------------------
# 6. sh2eb-elf GCC stage 2 (full build with newlib)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-gcc2 && cd /tmp/build-sh-gcc2 && \
/tmp/gcc-${GCC_VER}/configure \
--target=sh2eb-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--enable-languages=c,c++ \
--with-newlib \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-libssp \
--disable-libgomp \
--disable-libquadmath \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-sh-gcc2
# ---------------------------------------------------------------------------
# 7. m68k-elf binutils (Saturn 68EC000 sound CPU)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-m68k-binutils && cd /tmp/build-m68k-binutils && \
/tmp/binutils-${BINUTILS_VER}/configure \
--target=m68k-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--disable-nls \
--disable-multilib \
--disable-werror \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/build-m68k-binutils
# ---------------------------------------------------------------------------
# 8. m68k-elf GCC (compiler only; Yaul provides its own sound startup)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-m68k-gcc && cd /tmp/build-m68k-gcc && \
/tmp/gcc-${GCC_VER}/configure \
--target=m68k-elf \
--prefix="${YAUL_INSTALL_ROOT}" \
--enable-languages=c \
--without-headers \
--with-newlib \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-libssp \
&& make -j"$(nproc)" all-gcc && make install-gcc && \
rm -rf /tmp/build-m68k-gcc
# Clean up source tarballs/trees
RUN rm -rf /tmp/binutils-${BINUTILS_VER} /tmp/gcc-${GCC_VER} /tmp/newlib-${NEWLIB_VER}
# ---------------------------------------------------------------------------
# 9. Create m68keb-elf symlinks
# Yaul expects YAUL_ARCH_M68K_PREFIX=m68keb-elf but we built m68k-elf.
# m68k is always big-endian, so m68k-elf == m68keb-elf semantically.
# ---------------------------------------------------------------------------
RUN for tool in "${YAUL_INSTALL_ROOT}/bin/m68k-elf-"*; do \
base="$(basename "$tool")"; \
newname="${YAUL_INSTALL_ROOT}/bin/m68keb-elf-${base#m68k-elf-}"; \
ln -sf "$tool" "$newname"; \
done
# ---------------------------------------------------------------------------
# 10. Clone and install libyaul
# ---------------------------------------------------------------------------
RUN git clone --depth 1 --recurse-submodules \
https://github.com/yaul-org/libyaul.git /tmp/yaul && \
cd /tmp/yaul && \
YAUL_INSTALL_ROOT="${YAUL_INSTALL_ROOT}" \
YAUL_ARCH_SH_PREFIX=sh2eb-elf \
YAUL_PROG_SH_PREFIX=sh2eb-elf \
YAUL_ARCH_M68K_PREFIX=m68keb-elf \
YAUL_BUILD_ROOT=/tmp/yaul-build \
YAUL_BUILD=build \
YAUL_OPTION_MALLOC_IMPL=tlsf \
make install && \
rm -rf /tmp/yaul /tmp/yaul-build
# ---------------------------------------------------------------------------
# 11. Cross-compile zlib for sh2eb-elf
# Install into ${YAUL_INSTALL_ROOT}/sh2eb-elf/ to match the Yaul sysroot
# layout: headers at .../sh2eb-elf/include, libs at .../sh2eb-elf/lib.
# ---------------------------------------------------------------------------
RUN wget -q https://zlib.net/zlib-1.3.1.tar.gz -O /tmp/zlib.tar.gz && \
tar xf /tmp/zlib.tar.gz -C /tmp && \
cd /tmp/zlib-1.3.1 && \
CC="${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc" \
AR="${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar" \
RANLIB="${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ranlib" \
CFLAGS="-m2 -mb -fno-builtin -O2" \
./configure \
--prefix="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
--static \
&& make -j"$(nproc)" && make install && \
rm -rf /tmp/zlib-1.3.1 /tmp/zlib.tar.gz
# ---------------------------------------------------------------------------
# 12. Cross-compile libzip for sh2eb-elf
# ---------------------------------------------------------------------------
RUN printf '%s\n' \
'set(CMAKE_SYSTEM_NAME Generic)' \
'set(CMAKE_SYSTEM_PROCESSOR sh2)' \
"set(CMAKE_C_COMPILER \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc\")" \
"set(CMAKE_AR \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar\")" \
"set(CMAKE_RANLIB \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ranlib\")" \
'set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)' \
'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' \
'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)' \
'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)' \
> /tmp/sat-xc.cmake
RUN wget -q https://libzip.org/download/libzip-1.10.1.tar.gz -O /tmp/libzip.tar.gz && \
tar xf /tmp/libzip.tar.gz -C /tmp && \
cmake -S /tmp/libzip-1.10.1 -B /tmp/libzip-build \
-DCMAKE_TOOLCHAIN_FILE=/tmp/sat-xc.cmake \
-DCMAKE_INSTALL_PREFIX="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
-DCMAKE_FIND_ROOT_PATH="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
-DCMAKE_C_FLAGS="-m2 -mb -fno-builtin -O2" \
-DZLIB_LIBRARY="${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/libz.a" \
-DZLIB_INCLUDE_DIR="${YAUL_INSTALL_ROOT}/sh2eb-elf/include" \
-DENABLE_BZIP2=OFF \
-DENABLE_LZMA=OFF \
-DENABLE_ZSTD=OFF \
-DENABLE_OPENSSL=OFF \
-DENABLE_GNUTLS=OFF \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_EXAMPLES=OFF \
-DBUILD_DOCUMENTATION=OFF \
-DBUILD_REGRESS=OFF \
-DBUILD_TOOLS=OFF \
&& cmake --build /tmp/libzip-build -- -j"$(nproc)" \
&& cmake --install /tmp/libzip-build \
&& rm -rf /tmp/libzip-1.10.1 /tmp/libzip.tar.gz /tmp/libzip-build /tmp/sat-xc.cmake
WORKDIR /workdir
VOLUME ["/workdir"]
+63 -12
View File
@@ -1,13 +1,64 @@
FROM vitasdk/vitasdk:latest
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
cmake \
git \
curl \
sudo \
wget \
libarchive-tools \
python3 \
python3-pip \
python3-dotenv \
python3-polib \
python3-pil \
python3-pyqt5 \
python3-opengl \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/vitasdk/vdpm /vdpm
WORKDIR /vdpm
RUN ./bootstrap-vitasdk.sh
ENV VITASDK=/usr/local/vitasdk
ENV PATH="${VITASDK}/bin:${PATH}"
RUN git clone https://github.com/vitasdk/packages.git /vitapackages
WORKDIR /vitapackages
RUN bash -lc '\
dir_array=( \
zlib \
bzip2 \
henkaku \
taihen \
kubridge \
openal-soft \
openssl \
curl \
curlpp \
expat \
opus \
opusfile \
glm \
kuio \
vitaShaRK \
libmathneon \
vitaGL \
SceShaccCgExt \
sdl2 \
libzip \
luajit \
); \
curdir=$(pwd); \
for d in "${dir_array[@]}"; do \
echo "${curdir}/${d}"; \
cd "${curdir}/${d}"; \
vita-makepkg; \
vdpm *-arm.tar.xz; \
done \
'
WORKDIR /workdir
# Install vitaGL and its dependencies (vitashark, SceShaccCg) via vdpm
RUN which vdpm
# Install Python (needed for Dusk code generation tools)
RUN apk add --no-cache \
python3 \
py3-pip \
py3-dotenv
VOLUME ["/workdir"]
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh"
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh"
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube-iso.sh"
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
if [ -z "$DEVKITPRO" ]; then
echo "DEVKITPRO environment variable is not set. Please set it to the path of your DEVKITPRO installation."
exit 1
fi
mkdir -p build-gamecube-iso
cmake -S. -Bbuild-gamecube-iso \
-DDUSK_TARGET_SYSTEM=gamecube \
-DDUSK_DOLPHIN_BUILD_TYPE=ISO \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake"
cd build-gamecube-iso
make -j$(nproc) VERBOSE=1
+1 -5
View File
@@ -5,10 +5,6 @@ if [ -z "$DEVKITPRO" ]; then
fi
mkdir -p build-gamecube
cmake -S. -Bbuild-gamecube \
-DDUSK_TARGET_SYSTEM=gamecube \
-DDUSK_DOLPHIN_BUILD_TYPE=DOL \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake" \
-DDKP_OGC_PLATFORM_LIBRARY=libogc2
cmake -S. -Bbuild-gamecube -DDUSK_TARGET_SYSTEM=gamecube -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake"
cd build-gamecube
make -j$(nproc) VERBOSE=1
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-knulli -f docker/knulli/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"
docker run --rm -v $(pwd):/workdir dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"
-1
View File
@@ -1,6 +1,5 @@
#!/bin/bash
cmake -S . -B build-knulli -G Ninja \
-DDUSK_BUILD_TESTS=ON \
-DDUSK_TARGET_SYSTEM=knulli \
-DCMAKE_TOOLCHAIN_FILE=./cmake/toolchains/aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/build-linux.sh"
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/build-linux.sh"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-psp -f docker/psp/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-psp /bin/bash -c "./scripts/build-psp.sh"
docker run --rm -v $(pwd):/workdir dusk-psp /bin/bash -c "./scripts/build-psp.sh"
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
docker build -t dusk-saturn -f docker/saturn/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-saturn /bin/bash -c "./scripts/build-saturn.sh"
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
set -e
if [ -z "$YAUL_INSTALL_ROOT" ]; then
if [ -d "/opt/yaul" ]; then
export YAUL_INSTALL_ROOT=/opt/yaul
else
echo "YAUL_INSTALL_ROOT is not set. Please set it to your Yaul SDK installation."
exit 1
fi
fi
mkdir -p build-saturn
cmake -S . -B build-saturn \
-DDUSK_TARGET_SYSTEM=saturn \
-DCMAKE_TOOLCHAIN_FILE="cmake/toolchains/saturn.cmake" \
-DYAUL_INSTALL_ROOT="${YAUL_INSTALL_ROOT}"
cmake --build build-saturn -- -j"$(nproc)"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-vita -f docker/vita/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-vita /bin/bash -c "./scripts/build-vita.sh"
docker run --rm -v $(pwd):/workdir dusk-vita /bin/bash -c "./scripts/build-vita.sh"
+4 -11
View File
@@ -1,13 +1,6 @@
#!/bin/bash
if [ -z "$VITASDK" ]; then
echo "VITASDK environment variable is not set. Please set it to the path of your VitaSDK installation."
exit 1
fi
mkdir -p build-vita
cd build-vita
cmake \
-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake \
cd /workdir
cmake -S . -B build-vita \
-DDUSK_TARGET_SYSTEM=vita \
..
make -j$(nproc)
-DCMAKE_TOOLCHAIN_FILE="$VITASDK/share/vita.toolchain.cmake"
cmake --build build-vita -- -j$(nproc)
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii.sh"
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-wii.sh"
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii-iso.sh"
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
if [ -z "$DEVKITPRO" ]; then
echo "DEVKITPRO environment variable is not set. Please set it to the path of your DEVKITPRO installation."
exit 1
fi
mkdir -p build-wii-iso
cmake -S. -Bbuild-wii-iso \
-DDUSK_TARGET_SYSTEM=wii \
-DDUSK_DOLPHIN_BUILD_TYPE=ISO \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake"
cd build-wii-iso
make -j$(nproc) VERBOSE=1
+1 -5
View File
@@ -5,10 +5,6 @@ if [ -z "$DEVKITPRO" ]; then
fi
mkdir -p build-wii
cmake -S. -Bbuild-wii \
-DDUSK_TARGET_SYSTEM=wii \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake" \
-DDUSK_DOLPHIN_BUILD_TYPE=DOL
cmake -S. -Bbuild-wii -DDUSK_TARGET_SYSTEM=wii -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake"
cd build-wii
make -j$(nproc) VERBOSE=1
mv Dusk.dol boot.dol
-61
View File
@@ -1,61 +0,0 @@
#!/bin/bash
# psp-debug.sh — build (optional), reset, and run Dusk on a live PSP.
#
# Prerequisites:
# usbhostfs_pc running (served from the build-psp/ dir, as host0:).
# PSP must be running psplink (visible on PSP screen).
#
# Usage:
# ./scripts/psp-debug.sh # reset + run
# ./scripts/psp-debug.sh --build # rebuild via docker first, then run
#
# PSP stdout streams to this terminal via pspsh (logDebug, printf, etc.).
# Run from a real terminal — pspsh needs stdin connected to a TTY.
#
# IMPORTANT: crashes put the PSP hardware in a bad state.
# Always reset before relaunching. If reset doesn't respond, power-cycle the PSP.
set -euo pipefail
PSPSH="pspsh"
PRX_HOST="host0:/Dusk.prx"
RESET_SLEEP=3
# ---- rebuild (optional) -----------------------------------------------------
if [[ "${1:-}" == "--build" || "${1:-}" == "-b" ]]; then
echo "==> Building PSP (docker)..."
"$(dirname "$0")/build-psp-docker.sh"
echo ""
fi
# ---- sanity checks ----------------------------------------------------------
if ! command -v "$PSPSH" &>/dev/null; then
echo "ERROR: pspsh not found in PATH"
echo " Add /home/yourwishes/pspdev/bin to PATH or set PSPSH= in this script."
exit 1
fi
if ! nc -z localhost 10000 2>/dev/null; then
echo "ERROR: psplink is not reachable on localhost:10000."
echo " Ensure usbhostfs_pc is running and the PSP shows the psplink prompt."
exit 1
fi
# ---- reset any running process ----------------------------------------------
echo "==> Resetting PSP..."
"$PSPSH" -e "reset"
echo " Waiting ${RESET_SLEEP}s for PSP to settle..."
sleep "$RESET_SLEEP"
# ---- launch + stream output -------------------------------------------------
# Pre-send the exec command, then relay /dev/tty so pspsh keeps a live stdin.
# PSP stdout flows through pspsh directly to this terminal.
# Ctrl-C to stop.
echo "==> Launching $PRX_HOST"
echo " (PSP stdout streams here — Ctrl-C to stop)"
echo "------------------------------------------------------------"
{ printf 'exec %s\n' "$PRX_HOST"; cat /dev/tty; } | "$PSPSH"
+1 -5
View File
@@ -1,7 +1,3 @@
#!/bin/bash
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run \
--rm \
-v "${GITHUB_WORKSPACE}:/workdir" \
dusk-linux \
/bin/bash -c "./scripts/test-linux.sh"
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/test-linux.sh"
-2
View File
@@ -1,6 +1,4 @@
#!/bin/bash
set -e
rm -rf build-tests
cmake -S . -B build-tests -DDUSK_BUILD_TESTS=ON -DDUSK_TARGET_SYSTEM=linux
cmake --build build-tests -- -j$(nproc)
ctest --output-on-failure --test-dir build-tests
+2 -9
View File
@@ -5,7 +5,7 @@
add_subdirectory(dusk)
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli" OR DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(dusklinux)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
@@ -13,16 +13,9 @@ if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(duskpsp)
add_subdirectory(dusksdl2)
elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(duskvita)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
add_subdirectory(duskdolphin)
elseif(DUSK_TARGET_SYSTEM STREQUAL "saturn")
add_subdirectory(dusksat)
endif()
+24 -31
View File
@@ -14,29 +14,18 @@ if(NOT libzip_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC zip)
endif()
if(NOT stb_image_FOUND)
find_package(stb REQUIRED)
if(STB_IMAGE_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC stb_image)
else()
message(FATAL_ERROR "stb_image not found. Please ensure stb is correctly fetched.")
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
endif()
endif()
if(NOT yyjson_FOUND)
find_package(yyjson REQUIRED)
if(yyjson_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC yyjson::yyjson)
else()
message(FATAL_ERROR "yyjson not found. Please ensure yyjson is correctly fetched.")
endif()
endif()
if(DUSK_BACKTRACE)
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_BACKTRACE
)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif()
# Includes
@@ -51,24 +40,28 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
main.c
)
# Defs
dusk_env_to_h(duskdefs.env duskdefs.h)
# Subdirs
add_subdirectory(animation)
add_subdirectory(event)
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(log)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(item)
add_subdirectory(locale)
add_subdirectory(rpg)
add_subdirectory(map)
add_subdirectory(scene)
add_subdirectory(system)
add_subdirectory(script)
add_subdirectory(story)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(network)
add_subdirectory(save)
add_subdirectory(util)
add_subdirectory(thread)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()

Some files were not shown because too many files have changed in this diff Show More