| CRM64Pro GDK v0.18.0 A free cross-platform game development kit built on top of SDL 3.0

Loading...
Searching...
No Matches
Reference Guide

# 1. Introduction

CRM64Pro is a modern, lightweight and hardware-accelerated C++17 Game Development Kit built on top of SDL3. It provides a clean, modular architecture for creating 2D games, tools, and interactive applications, combining deterministic timing, CDCv2 asset packaging, GUI, audio, H.264 video playback, networking and cross-platform support.

CRM64Pro is distributed under the zlib license.

### Modern core Developed in C++17 and powered by SDL3, featuring multi-threading, advanced logging utilities, CDCv2 asset archives with compression, >4GB file support, validation, encrypted storage, streaming block I/O and integrated EditorC64 tooling. | ### Complete toolset Includes an advanced Scene and world management system with native Tiled map support, animated tilesets generated from Tiled or EditorC64 imports, including animated GIF and H.264 video sources, a full GUI windowing system, and a robust Sprite engine. | ### Hardware accelerated Supports Direct3D 11/12, Vulkan, Metal, and OpenGL, ensuring fast and smooth rendering across all supported platforms.

Developed by MegaStorm Systems. Visit the official website for updates, releases, and complete documentation. |

# 2. Supported platforms

The GDK runs on major operating systems. Thanks to SDL3, it supports a wide variety of hardware-accelerated rendering and low-latency audio drivers.

Platform | Requirements | Render drivers | Audio drivers
--- | --- | --- | ---
| Windows 7, 10 and 11 (64-bit only) | Direct3D 9 Direct3D 11 Direct3D 12 Vulkan GPU OpenGL Software | WASAPI DirectSound
| Kernel 4.18+ (64-bit only) | Vulkan GPU OpenGL X11 Wayland Software | PulseAudio ALSA
| Android 5.0+ (API 21+) | OpenGL ES Vulkan | AAudio OpenSL ES
| macOS 10.13+ (High Sierra or later for x86_64) macOS 11+ (Big Sur or later for arm64) | Metal Vulkan GPU OpenGL Software | CoreAudio

# 3. Installation

This section describes how to install and configure the CRM64Pro GDK for development on the supported platforms.

## 3.1. Windows (Visual Studio 2022+)

|  | ### Windows Development Executable Installer (Inno Setup)

1. Download and Install CRM64Pro is distributed as a standard Windows installer created with Inno Setup (for example: `CRM64Pro-X.Y.Z-win-x64.exe`).

The installer will deploy headers, libraries and the EditorC64 into the selected destination.

The following libraries are provided:

- `CRM64Pro.lib` – Dynamic linking (DLL import library)
- `CRM64Pro.static.lib` – Static linking (Release)
- `CRM64Pro.static-debug.lib` – Static linking with debug information

2. Configure Visual Studio No global system variables are required. Configure your project locally:

1. Right-click your project in Solution Explorer and select Properties.
2. Select All Configurations and x64 platform.
3. Add the CRM64Pro `include/` directory to C/C++ → General → Additional Include Directories.
4. Add the CRM64Pro `lib/` directory to Linker → General → Additional Library Directories.
5. Add one of the following to Linker → Input → Additional Dependencies:
   - `CRM64Pro.lib` (Dynamic linking)
   - `CRM64Pro.static.lib` or `CRM64Pro.static-debug.lib` (Static linking)

3. Runtime (DLL) When using dynamic linking, ensure that `CRM64Pro.dll` is located next to your executable or available in the system PATH (the installer allows to update this automatically).

## 3.2. Linux (GCC 13+)

|  | ### Linux Development Self-Extracting Bash Installer

1. Download and Install CRM64Pro for Linux is distributed as a self-extracting Bash installer (for example: `CRM64Pro-X.Y.Z-linux-x64.sh`).

Run the installer from a terminal:

chmod +x CRM64Pro-X.Y.Z-linux-x64.sh
sudo ./CRM64Pro-X.Y.Z-linux-x64.sh

The installer places headers and libraries in standard system locations (e.g. `/usr/local/include` and `/usr/local/lib`).

2. Compiling Once installed, CRM64Pro can be linked using standard compiler flags:

g++ main.cpp -o mygame -lCRM64Pro

## 3.3. macOS (Clang 12+)

|  | ### macOS Development DMG Package (Universal Binary)

1. Download and Install CRM64Pro is distributed as a DMG package. The installer provides a universal library supporting both x86_64 and arm64 (Apple Silicon) architectures.

Headers and libraries are installed into standard locations (e.g. `/usr/local` or `/Library`).

2. Compiling You can compile from the terminal or configure Xcode normally:

clang++ main.cpp -o mygame -lCRM64Pro

# 4. Getting started

Ready to write code? Jump straight into development with the practical examples.

| First time using the GDK? Follow the step-by-step guides to create your first window, render sprites, play audio files, etc. | View Tutorials >>

# 5. Game architecture

CRM64Pro abstracts the complexity of modern game loops into a flexible "Governor" pattern (Main::update). This allows developers to choose the synchronization strategy that best fits their game genre, from simple variable time-step loops to high-precision interpolated fixed-step systems.

Default Threading Model
CRM64Pro uses internal worker threads for selected subsystems, but public module APIs must be called from the main thread unless a module explicitly documents thread-safe access:

Subsystem | Execution Context | Description
--- | --- | ---
Audio | Internal mixer thread | SDL3_mixer performs audio mixing in parallel. Public AudioTrack and AudioTrackMgr calls are main-thread API calls.
Network | Background threads | Sockets run asynchronously. Public NetTCP calls are main-thread API calls unless explicitly documented otherwise.
Graphics | Main thread | Due to underlying GPU API constraints (Direct3D/OpenGL), rendering commands must be issued from the main thread.
Logic | Flexible | Runs on a high-precision fixed-timestep for deterministic behavior. The Scene system uses this for ultra-smooth sub-pixel movement.

| ### Game loop methods From basic to professional approaches

Timing values
Main exposes three timing values. They are intentionally separate because logic updates and rendering do not always run at the same rate:

Method | Use it for | Fixed logic mode | Variable logic mode
--- | --- | --- | ---
Main::getLogicDeltaTime() | Game simulation, physics, AI, particles, Scene updates and playback advancement. | Returns the fixed logic step, exactly 1.0 / LFR, for every consumed logic update. | Returns the current frame delta.
Main::getInterpolationFactor() | Render-only interpolation between the previous and current logic state. | Returns the accumulator fraction in the [0.0, 1.0) range. | Returns 0.0.
Main::getDeltaTime() | Wall-clock frame elapsed time, profiling, diagnostics, or effects that intentionally depend on real frame time. | Returns the last engine-frame elapsed time; this is not the logic step. | Returns the current frame delta.

> Rule:
> Use Main::getLogicDeltaTime() inside logic code. Use Main::getInterpolationFactor() only while rendering. Use Main::getDeltaTime() only when actual elapsed wall-clock frame time is required.

Scene updates and Video playback are advanced internally with Main::getLogicDeltaTime(), so their speed is independent from render rate. Sprite position interpolation uses Main::getInterpolationFactor() during rendering; Sprite animation timing remains internally managed by the Sprite module.

## 5.1. Variable Time Step (Synchronous)

This is the classic "render-as-fast-as-possible" loop. Logic and Graphics updates occur sequentially in the same iteration. The "Main loop" will run at the maximum speed provided by the CPU.

- Mechanism: update() processes events and returns control codes. You run logic when it returns 0, and render when the engine emits ::EC_RENDER.
- Pros: Extremely simple to implement and debug.
- Cons: Physics and game speed are tied to framerate. Requires carefully managed "Delta Time" for movement, or the game will run faster on better hardware.
- Best for: Simple demos, visual novels, or learning projects.

> Performance Note:
> You can use CRM64Pro::ConfigMgr::setMTFriendly() to specify a minimum wait time (milliseconds) per frame. This yields execution control back to the operating system to avoid unnecessary 100% CPU usage.
> Warning: Do not try to use this parameter for "time control" (e.g., setting it to 10ms to get 100fps). OS scheduling is not guaranteed; some iterations could take 10ms and others more, breaking the "smoothness" of the loop.

Code Example: Variable Step
Main &mC64 = Main::instance();
SDL_Event myEvent;
Sint32 iUpdate = 1;
Uint8 bRunning = 1;
while(bRunning)
{
// 1. Engine Governor (Process Input/Events)
while((iUpdate = mC64.update(&myEvent)) != 0)
{
if(iUpdate == SDL_EVENT_QUIT || myEvent.type == SDL_EVENT_QUIT) bRunning = 0;
}
if(!bRunning) break;
// 2. Logic
float fDeltaTime = mC64.getLogicDeltaTime();
MyGame_Update(fDeltaTime);
// 3. Render
// MyGame_Draw();
}

## 5.2. Fixed Time Step (Asynchronous)

In this mode, the Logic runs at a guaranteed deterministic rate (e.g., 20 Hz), while Rendering runs at the monitor's refresh rate (e.g., 144 Hz or VSync).

- Mechanism: You configure timer() with a target rate. The update() method manages the accumulator and triggers logic updates only when required. Graphics are executed via the ::EC_RENDER event.
- Pros: Deterministic physics (essential for collision stability). Logic speed is independent of framerate.
- Cons: Without interpolation, moving objects may appear to "jitter" or "stutter" if the render rate doesn't perfectly match the logic rate.
- Best for: GUI-heavy applications.

> Performance Note:
> In this mode, the graphics logic runs without limit (max CPU/GPU speed). On fast systems, CRM64Pro::ConfigMgr::setMTFriendly() can still be used to yield execution back to the OS and avoid 100% CPU usage on unnecessary rendering frames.

Code Example: Fixed Step
Main &mC64 = Main::instance();
// Configure Logic to run exactly 60 times per second
mC64.timer().init();
mC64.timer().setRate(0, 60);
SDL_Event myEvent;
Sint32 iUpdate = 1;
Uint8 bRunning = 1;
while(bRunning)
{
// 1. Engine Governor
while((iUpdate = mC64.update(&myEvent)) != 0)
{
switch(myEvent.type)
{
case SDL_EVENT_QUIT: bRunning = 0; break;
// 2. Render (Triggered freely, e.g., at 144Hz)
case ET_C64:
if(myEvent.user.code == EC_RENDER)
{
// MyGame_Draw();
}
break;
}
}
if(!bRunning) break;
// 3. Logic (Runs exactly 60Hz)
float fDeltaTime = mC64.getLogicDeltaTime();
MyGame_UpdateFixed(fDeltaTime);
}

## 5.3. Fixed Time Step with Interpolation (Professional)

This is the gold standard for action games. Logic runs at a fixed, low frequency (e.g., 20 or 30 Hz) for stability, but the Engine renders at maximum framerate by interpolating positions between the previous and current logic states.

- Mechanism: You provide a RenderCallback. The engine calls this automatically. CRM64Pro sprites and scrolling handle interpolation automatically.
- Pros: "Buttery smooth" motion even with low CPU usage. High-performance physics stability. Separation of concerns.
- Cons: Slightly more complex setup. You must not modify game state inside the render callback.
- Best for: High-performance 2D worlds, complex ARPGs, platformers, and any game requiring pixel-perfect physics and cinematic camera logic.

> Performance Note:
> Using the callback function will produce a very smooth graphics output. This callback function can be changed dynamically or disabled using nullptr.
> On fast systems, CRM64Pro::ConfigMgr::setMTFriendly() can still be used to avoid unnecessary 100% CPU usage.

Code Example: Interpolated
// Initialization stuff
Main &mC64 = Main::instance();
mC64.timer().init();
mC64.timer().setRate(0,20); // Set the Logic Frame Rate to 20
// ...
// Set a render callback for our main screen
Screen *mScreen = mC64.configMgr().get();
mScreen->setRenderCallback([](Sint32 iMode) -> Sint32
{
// My graphics stuff
// ...
return 0;
});
// Main loop
SDL_Event myEvent;
Sint32 iUpdate = 1;
Uint8 bRunning = 1;
while(bRunning)
{
// C64 main governor
while((iUpdate = mC64.update(&myEvent)) != 0)
{
switch(myEvent.type)
{
case SDL_EVENT_QUIT:
bRunning = 0;
break;
}
}
if(!bRunning) break;
// My logic stuff
float fDeltaTime = mC64.getLogicDeltaTime();
MyGame_UpdateFixed(fDeltaTime);
}

# 6. Configuration system

| ### Configuration system Built-in launcher and customization

CRM64Pro includes a built-in "Launcher" window (CRM64Pro::ConfigMgr::setup). This allows end-users to configure hardware settings (Resolution, Monitor, Audio Driver) before the engine initializes the full graphical context.

Default Setup Launcher

Key Features:

- Video Resolution & Window Mode selection.
- Graphics API & Monitor selection.
- Audio Driver selection & Output toggling.
- Automatic configuration saving/loading.

Workflow

1. Call setup(): Launches the GUI. Returns 0 if the user clicked "OK".
2. Call load(): Reads the saved configuration and initializes the system.

Code Example: Implementation
// Initialization stuff
Main &mC64 = Main::instance();
mC64.timer().init();
mC64.timer().setRate(0,20); // Set the Logic Frame Rate to 20
// Call to setup method
// Arguments: Config file to save to, Resource pack, Layout XML (optional)
Sint32 iRet = mC64.configMgr().setup("config.xml", "setup.cdc", "setup.xml");
// If configuration selected by user is saved and clicked on OK, then launch the application
if(iRet == 0)
{
if(mC64.configMgr().load("config.xml", "setup.cdc") == 0)
{
Screen* mScreen = mC64.configMgr().get();
if(mScreen && mScreen->show() == 0)
{
// main code
// ...
}
}
}

Customization via XML
The launcher layout is data-driven. You can provide a custom XML file (e.g., setup.xml) to override the default look. This allows you to:

- Change the logo (Sprite resource).
- Hide specific options (e.g., force VSync or specific resolutions).
- Rename window titles and labels.

Customized Launcher Example

In the example above, the window title was changed to "Validation Setup", the 5th resolution option was hidden, and the logo was replaced.

Code Example: setup.xml
<?xml version="1.0" encoding="UTF-8"?>
<c64_setup title="Validation Setup">
<!-- CRM64Pro GDK Setup Layout. MegaStorm Systems (c) -->
<main>
<logo state="1" name="milogo"/>
</main>
<general>
<wgMTfriendly value="10"/>
</general>
<video>
<wgScreenTitle text="AppCarrier"/>
<wgResolution5 state="5"/> <!-- Hide 5th resolution option -->
<wgSpecialVSync value="1"/>
<wgSpecialBatching value="0"/>
</video>
<audio>
<wgAudio value="0"/>
<wgSample32bits value="1"/>
</audio>
</c64_setup>
CRM64Pro
CRM64Pro GDK.
Definition AudioTrack.cpp:33

## 6.1 Master Setup XML Reference

The internal Master XML defines the structure. The element names cannot be modified, but their attributes can be overridden in your custom XML.

Attribute Reference

Attribute | Description
--- | ---
state | 0 (Disabled/Shown), 1 (Enabled/Visible) or 5 (Disabled/Hidden).
name | The C64 Sprite resource name (e.g., for logo).
x, y | Widget position. Supports CRM64Pro::ePositionHelper.
value | The default value for the widget (Checkbox/Slider).
text | The label text displayed to the user.
action | Used for specific widgets to open a file or URL.

View Master Setup XML
<?xml version="1.0" encoding="UTF-8"?>
<c64_setup title="Setup" cursor="default" icon_img="default" font="default">
<!-- CRM64Pro GDK Setup Layout. MegaStorm Systems (c) -->
<!-- Attributes and values:
state: 0(disabled but shown) , 1(enabled) or 5 (disabled and not shown).
name: the sprite resource name.
x and y: positions. Supports CRM64Pro::ePositionHelpers.
value: for widgets, it is the value.
text: for widgets, it is the text.
action: for widgets: open a file or an url.
-->
<!-- The size of the setup screen is set by the size of the main name resource -->
<main name="default" x="0" y="0">
<logo state="1" name="default" x="59" y="10"/>
<!-- CheckBox widgets group for Tabs -->
<wgTabsGeneral name="default" x="28" y="98" text="General"/>
<wgTabsVideo state="1" name="default" x="94" y="98" text="Video"/>
<wgTabsAudio state="1" name="default" x="160" y="98" text="Audio"/>
<!-- Button widgets -->
<wgSave state="1" name="default" x="44" y="401" text="Save"/>
<wgOK state="1" name="default" x="178" y="401" text="OK"/>
<wgExit state="1" name="default" x="313" y="401" text="Exit"/>
<!-- Label widget -->
<wgInfoCopyright state="1" x="70" y="439" text="Setup Tool - CRM64Pro GDK - MegaStorm Systems (c)"/>
</main>
<general name="default" x="28" y="115">
<!-- Label widgets -->
<wgInfoCPU state="1" x="43" y="37" text="Processor: "/>
<wgInfoMemory state="1" x="43" y="62" text="Memory: "/>
<wgInfoVideoCard state="1" x="43" y="87" text="Video card: "/>
<wgInfoAudioCard state="1" x="43" y="112" text="Audio card: "/>
<!-- CheckBox widget -->
<wgMTfriendly state="1" name="default" x="43" y="132" value="1" text=" Multitasking friendly"/>
<!-- Button widgets -->
<wgReadme state="1" name="default" x="73" y="209" text="Readme" action="readme.txt"/>
<wgWeb state="1" name="default" x="223" y="209" text="Website" action="http://www.megastormsystems.com"/>
</general>
<!-- Video configuration is only for "default" C64 screen -->
<video name="default" x="28" y="115">
<!-- Title for screen -->
<wgScreenTitle text="CRM64Pro GDK Application"/>
<!-- CheckBox widgets group for Renderers -->
<wgRendererSoftware state="1" name="default" x="70" y="15" value="1" text=" Software"/>
<wgRendererOpenGL state="1" name="default" x="142" y="15" value="0" text=" OpenGL"/>
<wgRendererVulkan state="1" name="default" x="214" y="15" value="0" text=" Vulkan"/>
<wgRendererGPU state="1" name="default" x="286" y="15" value="0" text=" GPU"/>
<wgRendererDirect3D9 state="1" name="default" x="70" y="35" value="0" text=" Direct3D9"/>
<wgRendererX11 state="1" name="default" x="70" y="35" value="0" text=" X11"/>
<wgRendererMetal state="1" name="default" x="70" y="35" value="0" text=" Metal"/>
<wgRendererDirect3D11 state="1" name="default" x="142" y="35" value="0" text=" Direct3D11"/>
<wgRendererDirect3D12 state="1" name="default" x="214" y="35" value="0" text=" Direct3D12"/>
<!-- CheckBox widgets group for Resolution -->
<wgResolution1 state="1" name="default" x="30" y="100" value="1" width="800" height="600" text=" 800x600"/>
<wgResolution2 state="1" name="default" x="30" y="120" value="0" width="1280" height="720" text=" 1280x720"/>
<wgResolution3 state="1" name="default" x="30" y="140" value="0" width="1920" height="1080" text=" 1920x1080"/>
<wgResolution4 state="1" name="default" x="30" y="160" value="0" width="2560" height="1440" text=" 2560x1440"/>
<wgResolution5 state="1" name="default" x="30" y="180" value="0" width="3840" height="2160" text=" 3840x2160"/>
<wgResolution6 state="5" name="default" x="30" y="200" value="0" width="c1" height="c1" text=" CustomRes"/>
<!-- CheckBox widgets group for Mode -->
<wgModeWindow state="1" name="default" x="130" y="100" value="1" text=" Window"/>
<wgModeFullscreen state="1" name="default" x="130" y="120" value="0" text=" Fullscreen"/>
<wgModeFullscreenExclusive state="1" name="default" x="130" y="140" value="0" text=" Fullscreen exclusive"/>
<!-- CheckBox widgets -->
<wgSpecialVSync state="1" name="default" x="255" y="100" value="0" text=" Vertical-Sync"/>
<wgSpecialBatching state="1" name="default" x="255" y="120" value="0" text=" Render-Batching"/>
</video>
<audio name="default" x="28" y="115">
<!-- CheckBox widget -->
<wgAudio state="1" name="default" x="100" y="11" value="1" text=" Enable audio"/>
<!-- CheckBox widgets group for Sampling -->
<wgSample8bits state="1" name="default" x="30" y="70" value="0" text=" 8bits"/>
<wgSample16bits state="1" name="default" x="30" y="90" value="1" text=" 16bits"/>
<wgSample32bits state="1" name="default" x="30" y="110" value="0" text=" 32bits"/>
<!-- CheckBox widgets group for Frequency -->
<wgFrequency22Hz state="1" name="default" x="125" y="70" value="0" text=" 22050Hz"/>
<wgFrequency44Hz state="1" name="default" x="125" y="90" value="1" text=" 44100Hz"/>
<wgFrequency48Hz state="1" name="default" x="125" y="110" value="0" text=" 48000Hz"/>
<!-- CheckBox widgets group for Mode -->
<wgModeStereo state="1" name="default" x="242" y="70" value="1" text=" Stereo"/>
<wgModeSurround state="1" name="default" x="242" y="90" value="0" text=" Surround 4ch"/>
<wgModeSurroundPlus state="1" name="default" x="242" y="110" value="0" text=" Surround 6ch"/>
<!-- Horizontal slider widgets -->
<wgVolumeMaster state="1" name="default" x="100" y="140" value="1.0" text="Master volume:"/>
<wgVolumeMusic state="1" name="default" x="100" y="170" value="1.0" text="Music volume:"/>
<wgVolumeSFX state="1" name="default" x="100" y="200" value="1.0" text="SFX volume:"/>
<wgVolumeVoice state="1" name="default" x="100" y="230" value="1.0" text="Voice volume:"/>
<wgVolumeCustom state="5" name="default" x="100" y="260" value="1.0" text="Custom volume:"/>
</audio>
</c64_setup>

# 7. High-level Architecture

CRM64Pro Architecture Diagram

# 8. Changelog

Log of all notable changes made to CRM64Pro GDK including the date, version, and brief description:

## 🚀 2026-06-16 – v0.18.0 – Support video playback, new CDCv2 and general improvements
### Added
- Video:
- Added the new Video module with FFmpeg-backed playback, CDC storage/loading, frame extraction, export support and synchronized audio streaming through AudioTrack.
- Supports H.264 video through FFmpeg demuxing, either as raw H.264 elementary streams or inside MP4, MOV, M4V, and AVI containers, with decoded YUV420P/YUVJ420P/NV12 video frames.
- EditorC64:
- Added Scene import tooling for creating maps/layers from Tiled TMX maps, images, supported video formats, static GIFs, and animated GIFs.
- Added frame-sequence-to-animated-tilemap conversion for supported video formats and animated GIFs, with tile deduplication, animated tile generation, and atlas/animation statistics.
### Changed
- Homogenized engine file operations around SDL3 filesystem/IO APIs, reducing platform-specific C runtime file handling and improving portability across desktop and mobile targets.
- Standardized public return contracts: handle-returning methods return ids > 0, status methods return 0 or a negative error code, and bool methods return true/false success states.
- Documented the main-thread public API convention, modules must be called from the main thread unless explicitly documented as thread-safe.
- Enforced exception-free allocation for `CMemAllocator` classes by deleting plain `new/new[]`; engine objects must use `new(std::nothrow)` and check for `nullptr`.
- Refined public return-contract policy: operations that touch SDL, allocation, resource ownership, audio devices, or screen creation now preserve diagnostic error codes
through Sint32 status returns, while simple setters and predicates remain bool or void.
- Archive:
- Replaced the CDC archive backend with the new CDCv2 format, adding support for >4GB files, stronger validation, lightweight confidentiality / non-authenticated encrypted storage for indexes and blocks, streaming reads and writes for raw blocks, safer defragmentation, and automatic CDCv1 upgrade support in EditorC64.
- Audio:
- Improved AudioTrack playback internals with proper ownership of overlapping mixer tracks, safer CDC/file validation, direct PCM video streaming
support, and more reliable live control updates for pitch, panning, position, tag, pause, resume, and cleanup.
- Updated dependencies:
- libpng 1.6.58 (Apr 2026)
- SDL3_mixer 3.2.0 (Mar 2026)
- dr_flac 0.13.6 (Jan 2026)
- libxmp 4.7.0 (Mar 2026)
- SDL 3.5.0 (May 2026)
- SDL3_image 3.5.0 (Jun 2026)
- ffmpeg 8.1.1 (May 2026)
### Fixed
- Tool:
- `fileOpenDialog` and `fileSaveDialog` methods now use SDL3 functions and support Windows, Linux, macOS and Android.
### Breaking Changes
- Standardized `setScroll()` and `setLayerPosition()` to return 0 on success or a negative error code on failure, changed/moved state is now reported through optional bool* output parameters.
- Renamed AudioTrack and Video playback position APIs to `seek()` and `getPlaybackPosition()`, and renamed AudioTrack 3D audio APIs to `setSpatialPosition()` and `setSpatialDistance()`.
- Renamed resource manager `exist()` methods to `exists()` across XML, AudioTrack, Image, Cursor, Tile, Sprite, Font, Video and Particle managers.
- Added `ePlaybackStatus` and changed `Video::status()` and `AudioTrack::status()` to use `PS_STOPPED`, `PS_PLAYING`, `PS_PAUSED` and `PS_FINISHED` playback states instead of `eGeneralStatus` values.
- Resource names are now validated explicitly and are no longer silently truncated when longer than the CDC/name limit.
- Replaced generic platform/environment macros with `CRM64PRO_PLATFORM_*` and `CRM64PRO_ENV_64BIT`, and removed 32-bit target support.
- Renamed macro `CRM64PRO_STATIC` to `CRM64PRO_LINK_STATIC`.
- Changed several public API methods from bool to Sint32: `Screen::show()`, `Screen::setIcon()`, `Screen::setDriver()`, `Screen::setMode()`, `Screen::setSize()`, `Image::assignSurface()`,
`AudioTrack::play()`, and `AudioTrack::fadeIn()`, callers must check result == 0 for success or result < 0 for failure.
- Changed `SceneObject::setSize()` and `SceneLayerObject::setSpatialGridCellSize()` from void to bool so invalid or unapplied changes can be detected.
- Main:
- Changed Main module accessors from I*() names to camelCase names (for example `ITimer()` -> `timer()`, `ILogMgr()` -> `logMgr()`, `IImageMgr()` -> `imageMgr()`).
- Changed `Screen::setRenderCallback()` to use a `function<Sint32(Sint32)>` signature instead of the old function-pointer plus void* user-data signature, which was removed.
- Renamed `ePositionHelpers` to `ePositionHelper`.
- Renamed `CMem::setLogLevel()` to `CMem::setStatsLevel()` to clarify that it controls memory statistics gathering, not log output.
- Renamed audio frequency enum values to value-based names: `CAF_NORMAL` -> `CAF_22K`, `CAF_HIGH` -> `CAF_44K`, and `CAF_ULTRA` -> `CAF_48K`.
- Tool:
- Removed legacy `Tool::xxHash*` 32-bit helpers, use the `Tool::xxHash3*` 64-bit API instead.
- Renamed message box API enums for consistency: `eToolMBT/TMBT_*` became `eMsgBoxType/MBT_*`, and `eMsgBoxButton` values `TMBB_*` became `MBB_*`.
- Renamed `Tool::XOR()` to `Tool::xorBuffer()` to follow camelCase method naming and describe the buffer operation clearly.
- Audio:
- Renamed `ConfigMgr::audioStatus()` to `ConfigMgr::audioIsReady()`.
- Font:
- Renamed `Font::setCursor()` and `Font::getCursor()` to `setTextCursor()` and `getTextCursor()`.
- Cursor:
- Renamed `CursorMgr::getSelect()` to `CursorMgr::getSelectedCursor()` for clearer cursor-selection API naming.
- Sprite:
- Split animation APIs into state-based methods and explicit `*Index()` row methods, game code should use animation states, while EditorC64/tools must use index methods for ordered animation rows.
- Network:
- Updated NetTCP result handling: moved most NR_* values to a dedicated range, aliased `NR_BAD_PARAMETER` and `NR_OUT_OF_MEMORY` to matching `C64_ERR_*` codes,
and changed `getClientsInfo()` and `getPendingWrites()` to return `eNetResult` with output parameters.
- Renamed `NetTCP::queryKillServer()` and `NetTCP::queryKillClient()` to `requestServerClose()` and `requestClientClose()` to distinguish command requests from data queries.
- Changed `NetTCP::sendData()` `bIsQuery` parameter from Uint8 to bool.
- GUI:
- Converted `Widget::disable()`, `Widget::enable()`, `Widget::show()`, `Widget::hide()`, `Widget::setMargin()`, `Widget::getMargin()`,
`Widget::setOnAction()`, `Widget::setOnHoverEnter()`, `Widget::setOnHoverExit()`, `Widget::setOnPressed()`, `Widget::setOnFocusLost()`,
`Widget::setOnValueCommitted()`, `Widget::setOnSelectionChanged()`, `Widget::setAlphaMod()`,
`Widget::setOnValueChanged()`, `Widget::setOnToggled()`, `Widget::setTooltipBackgroundColor()`, `Widget::getTooltipBackgroundColor()`,
`Widget::setTooltipBorderColor()`, `Widget::getTooltipBorderColor()`, `WidgetLabel::getText()`, `WidgetButton::setText()`,
`WidgetButton::getText()`, `WidgetButton::setKey()`, `WidgetCheckBox::setText()`, `WidgetCheckBox::getText()`, `WidgetCheckBox::setKey()`,
`WidgetCheckBox::setGroup()`, `WidgetProgress::setMargin()`, `WidgetSlider::setMargin()`, `WidgetTextEdit::setMargin()`,
`WidgetTextEdit::setText()`, `WidgetTextEdit::getText()`, `WidgetTextEdit::getScroll()`, `WidgetTextEdit::getVisibleItems()`,
`WidgetList::setMargin()`, `WidgetList::getScroll()`, `WidgetList::getVisibleItems()`, `WidgetComboBox::setMargin()`,
`WidgetComboBox::getScroll()`, and `WidgetComboBox::getVisibleItems()` from Sint32 status returns to bool success/failure returns.
## 🚀 2026-05-08 – v0.17.0 – GUI refactored and improved
### Added
- GUI:
- Added `Widget::getID()`, `Widget::getName()`, `Widget::getType()` and `Panel::getWidgetIDs()` for widget inspection and panel widget enumeration.
- Added `Panel::createWidgetByType()` and `Panel::getWidgetByID()` for editors, importers, and runtime-driven widget workflows.
- Added `Panel::getWidgetInfoList()` for lightweight widget inspection without generic widget lookup.
- Added typed widget creation/getter pairs such as `createProgress()`, `getProgress()`, `createSlider()`, `getSlider()`,
`createTextEdit()`, `getTextEdit()`, `createList()` and `getList()`.
- Added direct widget callbacks `setOnAction()`, `setOnHoverEnter()`, `setOnHoverExit()`, `setOnPressed()` and `setOnFocusLost()` as
a modern event-style alternative to polling SDL widget events.
- Added semantic widget callbacks `setOnValueCommitted()`, `setOnSelectionChanged()`, `setOnValueChanged()` and `setOnToggled()`,
matching the richer `EC_WIDGET_*` event model.
- Added runtime GUI theming with built-in Default, Dark, and Flat themes plus custom theme creation through GUIMgr.
- Added `GUITheme` and `GUIThemeSkin` APIs for per-widget-type, per-state procedural styling.
- Added `GUIMgr` theme management methods: `createTheme()`, `getTheme()`, `setGlobalTheme()`, `getGlobalTheme()`, `closeTheme()`,
`getThemeDefault()`, `getThemeDark()`, and `getThemeFlat()`.
- Added panel-level theme override through `Panel::setTheme()` and `Panel::getTheme()`.
- `WidgetList` supports interactive multi-selection with Ctrl+Click toggle, Shift+Click range selection, multi-row rendering,
and selection APIs (`setMultiSelect()`, `clearSelection()`, `getSelectedCount()`, `getSelectedItems()`).
- Added panel flow layouts with `PL_HFLOW` and `PL_VFLOW`, plus `Panel::setLayout()`, spacing, padding, justify, align,
and invalidation APIs, allowing modeless/modal/ephemeral panels to auto-arrange child widgets horizontally or
vertically while preserving existing panel behavior.
- Added `Widget::clearBackground()` to clear per-state background image/sprite assignments and reset linked inherited background overrides.
- New `WidgetFrame` (`WT_FRAME`), a lightweight themed primitive for horizontal/vertical lines and outline/filled rectangles.
- New `WidgetNumberEdit` (`WT_NUMBEREDIT`), a numeric text-edit control with integer float input support and configurable min/max/step.
- New `WidgetComboBox`, a themed single-selection drop-down control with popup item list, hover/selection highlighting and autosort.
- `WidgetTextEdit` and `WidgetNumberEdit` support persistent keyboard focus, mouse selection, Ctrl+A/C/X/V/Z/Y, selection-aware editing, and undo/redo.
- Standardized numeric widget APIs around `setValue()`, `getValue()`, `setRange(min,max)` and `getRange(&min,&max)`.
- Standardized selection widget APIs around `setSelectedIndex()`, `getSelectedIndex()`, `setSelectedText()` and `getSelectedText()`.
- Tool:
- New `strToFloat()` and `floatToStr()` methods.
- Font:
- FontMgr now exposes built-in GUI fonts by name through `getBuiltin()`, supporting the embedded Arial/Courier New
black/white 10/12 font set with simple names such as Arial12Black and CourierNew10White.
### Changed
- GUI:
- Widget was reduced to a true common base API. Widget-specific methods were removed from Widget and must now be called on
the corresponding concrete widget classes (`WidgetButton`, `WidgetCheckBox`, `WidgetProgress`, `WidgetSlider`,
`WidgetTextEdit`, `WidgetNumberEdit`, `WidgetList`, `WidgetComboBox`, etc.).
- Internal refactor of the GUI subsystem: normalized widget feature flags, improved widget/panel inspection APIs, extracted update/render helpers,
reduced panel/widget coupling, and reorganized widget implementation units.
- Widget appearance now resolves through a theme cascade: widget explicit override -> panel theme -> global theme -> built-in Default.
- Disabled widgets now remain fully mutable from code, so setters and content-management APIs (`setText()`, `setValue()`,
`addItem()`, `setSelectedIndex()`, text/list editing, etc.) still work while disabled, while user interaction remains blocked.
- Expanded legacy `eEventCode` widget events from action/hover/lost-focus only to a richer model with `EC_WIDGET_SELECTIONCHANGED`,
`EC_WIDGET_VALUECHANGED`, `EC_WIDGET_TOGGLED` and `EC_WIDGET_VALUECOMMITTED` for cleaner widget-state handling.
- Programmatic widget setters now follow a silent-update policy. User interaction and explicit edit commits emit events/callbacks,
while setter callers should run any required application logic directly.
- CheckBox group handling now follows radio-button behavior with silent programmatic selection and event/callback notification only
for user-driven changes.
### Fixed
- GUI:
- TextEdit and NumberEdit now clamp their height so the current font and vertical margins always leave room for at least one visible text line.
- List selection now auto-scrolls the selected item into view using centered reveal when possible.
- TextEdit and NumberEdit value-commit callbacks now fire on Enter and focus-loss commits without treating Enter as focus loss.
- Tooltip and skin color editor fields now apply committed text values on Enter as well as focus loss.
- Grouped CheckBox changes now emit consistent `EC_WIDGET_TOGGLED` events for user-driven group selection changes.
- GFX:
- Fixed filled circle rendering to avoid repeated side overdraw while improving performance with midpoint scanline filling.
### Breaking Changes
- Font:
- Reworked bitmap glyph metrics to derive bounds from image separators, added configurable space width via `setSpaceWidth()` and `getSpaceWidth()`,
Font CDC binary compatibility is broken so existing Fonts must be re-imported and re-saved or opened with the EditorC64 that will auto-update them.
- GUI:
- `EC_WIDGET_*` events now pack widget IDs/values directly in `SDL_Event::user.data1/data2`; decode them with `reinterpret_cast<intptr_t>(...)` instead of dereferencing them as pointers.
- Replaced legacy `WIDGET_FEATURE_*` macros with `eWidgetFeature` enum. `Widget::setFeatures()` and `Widget::getFeatures()` now use `eWidgetFeature` with bitwise operator support.
- Removed `Widget::setState()`. Use `Widget::enable()`, `Widget::disable()` for interactivity and `Widget::show()`, `Widget::hide()` for visibility.
- `Widget::disable()` no longer removes the widget from rendering. Disabled widgets now remain visible with their deactivated skin; use hide() to make a widget invisible.
- Removed `Widget::activate()` and `Widget::deactivate()` after consolidating the public widget lifecycle API around `enable()`, `disable()`, `show()`, and `hide()`.
- Generic `Panel::createWidget()` and `Panel::getWidget()` were removed. Use typed creation/getters for normal code, or
`createWidgetByType()` and `getWidgetByID()` for dynamic editor/importer scenarios.
- Legacy widget class naming was modernized. `WidgetProgressBar`, `WidgetHSlider`, `WidgetVSlider`, `WidgetTextBox`, and
`WidgetListBox` were renamed to `WidgetProgress`, `WidgetSlider`, `WidgetTextEdit`, and `WidgetList`.
- Legacy widget type enum names were removed from active API usage. Use `WT_PROGRESS`, `WT_SLIDER`, `WT_TEXTEDIT`, and `WT_LIST`.
- `Widget::setState()` was removed. Widget lifecycle now uses `enable()`, `disable()`, `show()`, and `hide()`.
- `Widget::activate()` and `deactivate()` were removed from the public API after consolidating widget lifecycle semantics.
- `Widget::disable()` semantics changed. Disabled widgets remain visible with their deactivated skin; use hide() to remove a
widget from rendering.
- `Widget::status()` for widgets now reports only `GS_DISABLED`, `GS_HIDDEN`, or `GS_SHOWN`.
- Removed legacy `Widget::setFunction()` and its state-bound void* callback model in favor of typed std::function-based widget callbacks.
- Removed `GUIMgr::getFontWhiteAA()` and `GUIMgr::getFontBlack()`, callers must now use `IFontMgr().getBuiltin()`.
- Widget-specific GUI behaviors were removed from eWidgetFeature. TextEdit now uses direct `setMultiline()` and `setReadOnly()` APIs,
List now uses `setAutoSort()` and `setMultiSelect()`, and Console now uses explicit read-only methods instead of widget feature flags.
- Renamed background styling methods for clarity:
- `setBgColor()` and `getBgColor()` to `setBackgroundColor()` and `getBackgroundColor()`
- `setBgImage()` and `getBgImage()` to `setBackgroundImage()` and `getBackgroundImage()`
- `setBgSprite()` and `getBgSprite()` to `setBackgroundSprite()` and `getBackgroundSprite()`
- Replaced `Panel::type(...)` with explicit panel-type APIs: `setPanelType()` and `getPanelType()`.
- Removed `Widget::setText()` and `Widget::getText()` from the base `Widget` API. Text methods now live only on text-capable
widgets such as `WidgetLabel`, `WidgetButton`, `WidgetCheckBox`, `WidgetTextEdit`, and `WidgetNumberEdit`.
- Added explicit panel title APIs `Panel::setTitle()` and `Panel::getTitle()`. Panel title text should no longer be assigned through `baseWidget().setText()`.
- Renamed ambiguous widget-specific value/range APIs:
- `WidgetCheckBox`: `setValue()` and `getValue()` to `setChecked()` and `isChecked()`
- `WidgetList`: `setValue()`, `getValue()` and `getRange()` to `setSelectedIndex()`, `getSelectedIndex()` and `getItemCount()`
- `WidgetTextEdit`: ambiguous `getValue()` and `getRange()` to `getCursorLine()` and `getLineCount()`
- `WidgetNumberEdit`: `setNumber()`, `getNumber()`, `setMinMax()` and `getMinMax()` to `setValue()`, `getValue()`, `setRange(min,max)` and `getRange(&min,&max)`
- `WidgetProgress`: `setRange(max)` and `getRange()` to `setRange(min,max)` and `getRange(&min,&max)`
- `WidgetSlider`: `setRange(count)` and `getRange()` to `setRange(min,max)` and `getRange(&min,&max)`, with value clamping now based on the explicit inclusive range
- `WidgetList`: `getSelectedItem()` to `getSelectedText()`, and added `setSelectedText()` for symmetry with `WidgetComboBox`
- `WidgetComboBox`: `getSelectedItem()` to `getSelectedText()`, and added `setSelectedText()`
## 🚀 2026-03-25 – v0.16.0 – Network refactored and improved
### Added
- NetTCP:
- Added asynchronous outbound queueing for client and server traffic.
- Added configurable TX soft-brake / backpressure limits via `setTXSoftBrake()` / `getTXSoftBrake()`.
- Added richer `info()` diagnostics for RX/TX queues, buffered data, pending writes, throughput and errors.
- Added server pending-login handling to process new connections without blocking active clients.
- Added public `eNetResult` and `eNetTCPFeature` enums.
- Added challenge-response login with server nonce exchange.
- Added optional AES-CTR session payload encryption negotiated through `setFeatures()`.
- Added bounded string password support for NetTCP login/server creation.
- Added stricter per-message payload validation for login and control messages.
- Tool:
- New xxHash3() methods for XXH3 64-bit hashing.
### Changed
- NetTCP:
- Refactored socket ownership so actual I/O is performed only by the dedicated network thread.
- Converted outbound queues to ring buffers and updated the inbound receive queue to ring-buffer behavior.
- Improved server loop responsiveness under TX backlog by using shorter wait intervals when outbound data is pending.
- Improved `info()` output with cleaner RX/TX naming, feature reporting and clearer queue/buffer diagnostics.
- Upgraded packet integrity from folded 32-bit hashes to full 64-bit xxHash3 while keeping the DataNet wire header size unchanged.
- Tool:
- randSeedPCG() and randPCG() now support optional caller-managed state.
- Updated validation tests (BasicSystem, Config, ConfigSetup) with platform-aware native hardware renderers.
- Updated dependencies:
- SDL3_net 3.0.0 (Jan 2026)
### Fixed
- NetTCP:
- Fixed server login flow so slow connectors no longer stall active clients.
- Fixed login/session edge cases including leaked login payloads, failed `LOGIN_ACCEPTED` rollback, and mismatched negotiated feature reporting.
- Fixed multiple thread-safety issues by moving shared lifecycle/state flags to atomic usage where required.
- Fixed several queue, shutdown and diagnostic inconsistencies in client/server runtime paths.
- Fixed stale documentation/comments and protocol validation issues across the refactored network layer.
- Alpha transparency loss in software renderer during clipped scaling (SDL3 issue #15147).
- Fixed `ConfigMgr::setup()` video configuration saving so existing config.xml files now correctly update renderer and resolution values.
### Breaking Changes
- NetTCP:
- Public NetTCP receive messages were renamed from `NETMSG_*` to `NM_*`.
- Public NetTCP result/error codes now use the new `eNetResult` enum with `NR_*` values.
- Replaced `features()` with:
- `setFeatures()`
- `getFeatures()`
- `sendData()`, `queryKillServer()`, `queryKillClient()`, `queryClientsInfo()` and related NetTCP methods now return `eNetResult`.
- `createServer()` and `connectTo()` now use `const string& sPassword` instead of the old integer password.
- Packet integrity now uses a 64-bit hash field internally while preserving the existing 24-byte DataNet serialized header.
- IP fields were upgraded from IPv4-only Uint32 values to `NetTCP::NetIPAddress`, `ClientInfo::iIP` became `ClientInfo::ipAddress` and `NetTCP::getIP()` now returns `NetIPAddress`.
## 🚀 2026-02-28 – v0.15.0 – Scene with Object support
`TileEngine` has been completely renamed to `Scene` across the entire codebase to better reflect its role as a robust advanced 2D game world management system.
### Added
- Scene:
- Object Layer support:
- Introduced native TMX object layers with support for rectangles, points, polygons and polylines.
- Added runtime access to object data (position, size, properties, type, name).
- Object factory system for creating custom game objects from TMX data.
- Added TMX template (.tx) load/save support.
- Object rotation: shape, tile rotation + rotated AABB supported.
- High-precision timing integration:
- Scene logic now fully supports the new Main fixed-timestep system.
- Deterministic update behavior regardless of frame rate.
- Sub-pixel camera movement:
- Floating-point world coordinates for camera and layer scrolling.
- Smooth pixel-perfect rendering without jitter during slow movement.
- Support for user-defined layers via class inheritance, the new `addLayer()` injection method, and custom type IDs (`TELT_USER_START`).
- Added camera methods, multiple modes, and configurable parameters for per-layer behavior (follow, bounds/clamp, smoothing, and axis controls).
- Implemented a suite of tutorials covering various game genres (RTS, RPG, Scrolling) to fully validate the TileEngine system.
- Added native TE_Object trigger zones with cross-layer + type-hash filtering + delayed one-shot STAY/EXIT cooldown flow.
- Physics:
- Implemented new Physics interface with frame-accurate pixel-perfect collision detection.
- Sprite:
- Added support for new animation types: `ST_ONESHOT_REVERSE` and `ST_RANDOM`.
- CMem:
- Added `MSL_DEBUG` level for detailed leak detection with unique Allocation IDs, memory dump previews, and
debugger breakpoint support via `setBreakOnAlloc()`.
- Tool:
- New PCG32 PRNG methods: `randSeedPCG()`, `randPCG()` and `randRealPCG()`.
- Image:
- Added getWidth() and getHeight() methods to Image objects for easy access to dimensions.
- Added loadFromSurface() method to create an Image directly from an existing SDL_Surface.
- Expanded custom STL memory allocator support to include queue, stack, priority_queue, multimap, list, and shared_ptr/stringbuf aliases.
- Implemented engine-wide memory allocation protection for critical PIMPL/Manager failures and graceful returns for resource allocations.
### Changed
- Scene:
- Internal architecture refactor:
- Major refactor of Scene core to separate logic, rendering and timing concerns.
- Simplified layer update/render flow aligned with Main canonical game loop.
- Logic / render synchronization:
- Scene `update()` now runs strictly on the fixed timestep.
- Interpolation moved to render phase using engine-provided delta helpers.
- Scrolling and parallax math:
- All scrolling, parallax and auto-scroll calculations now use floating-point precision.
- Code cleanup & optimization:
- Reduced redundant state, improved cache locality and simplified internal containers.
- Timer:
- `benchInfoEx()` method in throughput mode now perform unit conversion to thousands and millions for better readability.
- Refactored microbenchmark system to centralize statistical logic and implement consistent, unit-aware formatting (K/M scales) across console logs and HTML reports.
- Main:
- Canonical game loop: Refactored `update()` to use a high-precision accumulator system with nanosecond resolution, ensuring zero-drift timing and
deterministic simulation steps.
- Introduced `getDeltaTime()` and `getLogicTime()` to provide unified, high-precision timing across all engine modules.
- Spiral of death prevention: Implemented safety clamping on delta time to ensure engine stability during significant lag spikes or window interruptions.
- Implemented engine-wide restricted prefix policy (# and @): enforced on create/setName and loadFromFile, while allowed on loadFromBuffer and CDC load.
### Fixed
- Implemented over 70 stability fixes including memory safety, robust I/O validation, and null-pointer guards across all core modules.
- Scene:
- TMX loading issues:
- Fixed incorrect handling of transparent / colorkey tilesets.
- Layer update/render edge cases:
- Fixed inconsistencies when enabling/disabling layers at runtime.
- Sprite:
- Fixed visual glitches caused by desynchronized timing.
- Interpolation now relies on Main timing helpers.
- Fixed an issue with `Sprite::status()` when using one-shot animations.
- Image:
- Fixed a memory leak in `ImageMgr::load()` (CDC path).
### Breaking Changes
- Macros to enum migration:
- Scene:
- Replaced `TE_LAYERFLAG_*` macros with `eSceneLayerFeature` and `SLF__*`.
- Replaced `TE_TILESETTYPE_*` macros with `eSceneTileSetType` and `STST_*`.
- Log: Replaced `LM_*` macros with `eLogMode`. Added bitwise operator support via a reusable template.
- Memory Manager: Replaced `CMEM_MOD_*` macros with `eMemoryModule` and `CMM_*`.
- Updated `alloc()`, `calloc()`, `realloc()`, and `alloc_aligned()` methods.
- Tool: Replaced `TMBB_*` macros with `eMsgBoxButton`.
- C64 System Status: Replaced `C64_STATUS_*` macros with `eGeneralStatus` and `GS_*`.
- Sprite Types: Replaced `SPR_TYPE_*` macros with `eSpriteType` `ST_*`.
- Custom events: Replaced the legacy `#define C64_EVENT` macro with `eEventType` with values `ET_C64` replacing old `C64_EVENT`.
- Audio:
- `eAudioFreq` renamed to `eConfigAudioFreq`. Internal values renamed from `AF_*` to `CAF_*`.
- `eAudioSample` renamed to `eConfigAudioSample`. Internal values renamed from `AS_*` to `CAS_*`.
- `eAudioMode` renamed to `eConfigAudioMode`. Internal values renamed from `AM_*` to `CAM_*`.
- XML:
- Removed parameter-based `getAttribute()` and `setAttribute()` overloads.
- Replaced with explicit typed methods: `getAttribute[String|Int|Uint|Float|Bool]` and `setAttribute[String|Int|Uint|Float|Bool]`.
- API method updates:
- `Tool::messageBox()`: Now returns `eMsgBoxButton` instead of `Sint32`.
Internal failures now log a warning and return TMBB_NONE instead of negative error codes.
- `status()` methods: Updated return type from `Sint32` to `eGeneralStatus` across all classes (Log, Screen, Sprite, Widget, CursorMgr).
- Refactored `renderEx()` methods:
- Standardized the extended rendering API across all high-level graphics objects (Sprite, Tile, Font)
to support asymmetric scaling (X/Y) and improve parameter consistency.
- `Sprite::renderEx()`changed from (float fScale, ...) to (float fScaleX, float fScaleY, ...).
- `Font::renderEx()` changed from (..., float fScale, ...) to (..., float fScaleX, float fScaleY, ...).
- `Tile::renderEx()` changed from (Sint32 iTile, double dAngle, ...) to (Sint32 iTile, float fScaleX, float fScaleY, double dAngle, ...).
....

View the Complete Changelog.

# 9. License

CRM64Pro GDK
Copyright (C) 2013-2026 Roberto Prieto <contact@megastormsystems.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.