# Platform -- Linux and Knulli `DUSK_TARGET_SYSTEM`: `linux` / `knulli` Source layer: `src/dusklinux/` Renderer: OpenGL (Linux) / OpenGL ES via EGL (Knulli) --- ## Overview Linux is the primary development target. Knulli is a Linux-based handheld OS (e.g. Anbernic devices); it shares the `src/dusklinux/` layer entirely and differs only in the CMake target (OpenGL ES instead of desktop OpenGL, EGL instead of GLX, and no backtrace support). Both targets use SDL2 for windowing and input. The window is resizable on both (`DUSK_DISPLAY_SIZE_DYNAMIC`). --- ## Compile-time macros | Macro | Linux | Knulli | |-------|-------|--------| | `DUSK_LINUX` | yes | yes | | `DUSK_KNULLI` | no | yes | | `DUSK_SDL2` | yes | yes | | `DUSK_OPENGL` | yes | yes | | `DUSK_OPENGL_ES` | no | yes | | `DUSK_DISPLAY_SIZE_DYNAMIC` | yes | yes | | `DUSK_INPUT_KEYBOARD` | yes | yes | | `DUSK_INPUT_POINTER` | yes | yes | | `DUSK_INPUT_GAMEPAD` | yes | yes | | `DUSK_TIME_DYNAMIC` | yes | yes | | `DUSK_NETWORK_IPV6` | yes | no | | `DUSK_THREAD_PTHREAD` | yes | yes | | `DUSK_CONSOLE_POSIX` | yes | no | --- ## Display - Default logical resolution: **640x480** (`DUSK_DISPLAY_WIDTH_DEFAULT` / `DUSK_DISPLAY_HEIGHT_DEFAULT`); game content renders at `DUSK_DISPLAY_SCREEN_HEIGHT=240`. - Dynamic resize: the window can be resized at any time; the engine letterboxes/scales the logical framebuffer to fit. - Screen mode is configurable via `SCREEN.mode` (see `.claude/display-core.md`). - Knulli uses OpenGL ES (GLES2) linked via EGL. Avoid any desktop OpenGL extensions that are not in the ES2 core. --- ## Asset loading `dusk.dsk` is located by searching a list of paths relative to the current working directory: ```c static const char_t *ASSET_LINUX_SEARCH_PATHS[] = { "%s", "../%s", "../../%s", "data/%s", "../data/%s", NULL }; ``` The first path where `dusk.dsk` is found wins. No packaging step is required on Linux -- run from the build directory or the project root. --- ## Input All three input types are supported: - **Keyboard** -- SDL scancode array via `SDL_GetKeyboardState()`. - **Pointer** -- mouse position normalized to [0, 1], scroll axes. - **Gamepad** -- first available `SDL_GameController`; axes normalized to [-1, 1] with a 0.2 deadzone. See `.claude/input.md` for the full action/button API. --- ## Save system Save files are plain files written to disk. - Path: `./saves/save_N.dat` (override `SAVE_LINUX_PATH` to change the directory at CMake configure time). - Format: `SAVE_LINUX_FILE_FORMAT = "%s/save_%u.dat"` where `%u` is the slot index. - No OS-level dialog blocking -- saves are synchronous filesystem calls. - Endian: host byte order (little-endian on x86/ARM). --- ## Network - Connection is detected automatically via `getifaddrs()`. No explicit connect step is needed. - `networkRequestConnection` immediately calls `onConnected` if any non-loopback interface is up, `onFailed` otherwise. - IPv4 and IPv6 supported (`DUSK_NETWORK_IPV6`). --- ## Time - Tick source: `SDL_GetTicks64()`. - Real time: `clock_gettime(CLOCK_REALTIME)`. - Dynamic timestep enabled (`DUSK_TIME_DYNAMIC`). --- ## Threading pthreads (`DUSK_THREAD_PTHREAD`). Thread-local storage via `__thread`. --- ## Build and toolchain No cross-compiler needed -- use the host GCC/Clang. ```sh # Debug build cmake -B build -DDUSK_TARGET_SYSTEM=linux -DCMAKE_BUILD_TYPE=Debug cmake --build build # Knulli (cross-compile to aarch64) cmake -B build \ -DDUSK_TARGET_SYSTEM=knulli \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/aarch64-linux-gnu.cmake \ -DCMAKE_BUILD_TYPE=Release cmake --build build ``` Dependencies: `SDL2`, `OpenGL` (Linux) or `GLES2` + `EGL` (Knulli), `pthread`, `m`. --- ## Endianness Little-endian. Detected at CMake configure time via `TestBigEndian` and set as `DUSK_PLATFORM_ENDIAN_LITTLE` or `DUSK_PLATFORM_ENDIAN_BIG`. --- ## Gotchas - `DUSK_CONSOLE_POSIX` enables POSIX-specific assert backtracing (Linux only; Knulli does not set it). - Knulli does not set `DUSK_NETWORK_IPV6` -- IPv6 may not be available on handheld devices. - `DUSK_TIME_DYNAMIC` is set, so physics/networking skip dynamic sub-steps by checking `if(TIME.dynamicUpdate) return;`.