InteractiveX Engine Documentation (r0.2)

This document was written from the actual InteractiveX r0.2 source code, including the public headers, engine core, renderer, media modules, interface system, and debug examples.

InteractiveX is a real-time engine for Windows built in C/C++ on top of Direct3D 9.
At the current stage, the public systems are focused on 2D rendering and UI, but the project itself is not limited to games only: the engine can also power tools, graphical applications, test environments, and real-time software.

The source is the documentation.
This file exists to make that source easier to navigate.


What InteractiveX is

From the codebase today, InteractiveX already provides:

  • a Windows engine bootstrap (Engine_*)
  • a Direct3D9 renderer
  • a GUI drawing layer
  • a small widget set (button, label, panel, slider, layout)
  • scene management
  • mouse / keyboard / gamepad input
  • timer and frame stats
  • 2D camera
  • entities
  • tilemap rendering + AABB collision resolution
  • spritesheet animation
  • particles
  • binary save/load
  • audio through miniaudio
  • image loading
  • MPEG-1 video playback
  • splash screen
  • runtime transition overlays
  • helper modules like Math2D, RNG and assets path building

The engine already behaves like a general real-time runtime, not just a toy graphics layer.


How the engine boots

The actual startup flow in main.cpp + engine/core/engine.cpp is:

  1. Create a normal Win32 window.
  2. Call Engine_Initialize(hWnd).
  3. Call Video_Initialize(hWnd) and configure window/video options.
  4. Optionally call Splashscreen_Show(...).
  5. Enter a standard Win32 message loop.
  6. Every frame, call Engine_RunFrame().

Internally, Engine_Initialize() currently does all of this:

  • Renderer_Initialize(hWnd)
  • Input_Initialize()
  • Gamepad_Initialize()
  • Timer_Initialize()
  • Assets_Initialize()
  • MediaAudio_Initialize(hWnd)
  • Scene_Initialize()
  • Scene_RegisterDebugScenes()
  • Scene_SetCurrent(IX_SCENE_MENU)
  • Scene_OnEnterCurrent()

That means if you use the default engine bootstrap, the renderer, input, audio, assets, timer, and scene registry are already wired together for you.


Frame lifecycle

The current engine frame order in Engine_RunFrame() is:

Timer_BeginFrame();
Gamepad_Update();
Transition_Update(Timer_GetDeltaSeconds());
Scene_Update();

if (!Transition_IsActive() && Scene_HasPendingChange())
{
    Scene_SetCurrent(Scene_ConsumePendingChange());
    Scene_OnEnterCurrent();
}

Scene_Render();
Timer_EndFrame();
Input_BeginFrame();
Sleep(1);

What this means in practice

  • Input_WasKeyPressed() and Input_WasMouseButtonReleased() are valid during the current frame.
  • Scene updates happen before scene render.
  • Scene change requests are deferred until after update.
  • Transition overlays are updated before the scene is rendered.
  • Input edge flags are reset at the end of the frame by Input_BeginFrame().

Project layout

Main public folders:

engine/core/        engine systems
engine/interface/   widgets and UI structs
engine/media/       audio / image / video playback
engine/             renderer + gui primitives
engine/effects/     optional fire effect module
engine/debug/       example scenes demonstrating the systems

Recommended reading order if you want to understand the engine fast:

  1. main.cpp
  2. engine/core/engine.h/.cpp
  3. engine/core/scene.h/.cpp
  4. engine/renderer.h/.cpp
  5. engine/gui.h/.cpp
  6. engine/debug/*.cpp

The debug modules are real usage examples.


Color format

The engine uses ARGB 32-bit colors:

0xAARRGGBB

Examples:

0xFFFFFFFF // white
0xFF000000 // black
0xFFFF0000 // red
0xCC2A7ABA // semi-transparent blue
0x00FFFFFF // fully transparent white

Alpha is the highest byte.


A real minimal app

This is the practical startup pattern based on the shipped main.cpp:

#include <windows.h>
#include "engine/core/engine.h"
#include "engine/core/video.h"
#include "engine/core/splashscreen.h"

static const int IX_INITIAL_WIDTH  = 960;
static const int IX_INITIAL_HEIGHT = 640;

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_GETMINMAXINFO:
            Video_HandleGetMinMaxInfo(lParam);
            return 0;

        case WM_SIZING:
            Video_HandleSizing(wParam, lParam);
            return TRUE;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }

    Engine_HandleMessage(hWnd, msg, wParam, lParam);
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow)
{
    WNDCLASSEXA wc = {};
    wc.cbSize = sizeof(WNDCLASSEXA);
    wc.style = CS_CLASSDC;
    wc.lpfnWndProc = MsgProc;
    wc.hInstance = hInst;
    wc.lpszClassName = "InteractiveXWindow";
    RegisterClassExA(&wc);

    HWND hWnd = CreateWindowA(
        "InteractiveXWindow",
        "InteractiveX App",
        WS_OVERLAPPEDWINDOW,
        100, 100, IX_INITIAL_WIDTH, IX_INITIAL_HEIGHT,
        NULL, NULL, hInst, NULL);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    if (!Engine_Initialize(hWnd))
        return 0;

    Video_Initialize(hWnd);
    Video_SetResolution(IX_INITIAL_WIDTH, IX_INITIAL_HEIGHT);
    Video_SetFullscreen(0);
    Video_SetVSync(0);
    Video_LockResize(0);
    Video_SetMinSize(640, 480);
    Video_Apply();

    Splashscreen_Show(hWnd, NULL, 3.0f); // optional

    MSG msg = {};
    while (msg.message != WM_QUIT)
    {
        while (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        if (msg.message == WM_QUIT) break;
        Engine_RunFrame();
    }

    Engine_Shutdown();
    UnregisterClassA("InteractiveXWindow", hInst);
    return 0;
}

Assets and paths

The assets system starts from the executable folder and builds derived roots automatically.

Functions:

  • Assets_GetBasePath() → executable folder
  • Assets_GetAssetsRoot()<base>\assets
  • Assets_GetImagesRoot()<base>\assets\images
  • Assets_GetAudioRoot()<base>\assets\audio

Typical usage

char fullPath[MAX_PATH];
if (Assets_BuildPath("video/test.mpg", fullPath, sizeof(fullPath)))
{
    // fullPath becomes <exe>\assets\video\test.mpg
}

Important detail

Assets_BuildPath() prefixes the assets root.
So this:

Assets_BuildPath("music/theme.ogg", out, sizeof(out));

becomes:

<exe>\assets\music\theme.ogg
  • Assets_BuildImagePath("ui/button.png", ...)<exe>\assets\images\ui\button.png
  • Assets_BuildAudioPath("ui/click.wav", ...)<exe>\assets\audio\ui\click.wav

Scene system

Scenes are the normal way to structure an InteractiveX app.

A scene is a callback table:

typedef struct IXSceneCallbacks
{
    void (*onEnter)(void);
    void (*onExit)(void);
    void (*onInvalidateLayout)(void);
    void (*update)(int mouseX, int mouseY, int mouseDown, int mouseReleased);
    void (*render)(void);
    void (*handleMouseMove)(int x, int y);
    void (*handleDragBegin)(int x, int y);
    void (*handleDragEnd)(void);
    void (*handleKeyDown)(WPARAM key);
} IXSceneCallbacks;

Registering a scene

static void MyScene_OnEnter() {}
static void MyScene_Update(int mouseX, int mouseY, int mouseDown, int mouseReleased) {}
static void MyScene_Render() {}

void RegisterMyScene()
{
    IXSceneCallbacks cb = {};
    cb.onEnter = MyScene_OnEnter;
    cb.update  = MyScene_Update;
    cb.render  = MyScene_Render;
    Scene_Register(100, cb); // custom scene ids start at IX_SCENE_USER_BASE
}

Switching scenes

Immediate request:

Scene_RequestChange(101);

Animated transition:

Transition_FadeBlack(101, 0.4f);

Important practical rule

Scene_Render() already does:

Renderer_BeginFrame();
your_render_callback();
Renderer_EndFrame();

So inside a normal scene render() callback, do not call Renderer_BeginFrame() or Renderer_EndFrame() again.
Just draw.

Layout invalidation

onInvalidateLayout is called when the window is resized and WM_SIZE reaches Engine_HandleMessage().

Use it to reposition buttons/sliders/panels based on Renderer_GetOverlayContentRect() or Renderer_GetClientSize().


Renderer and GUI drawing

The renderer is the Direct3D9 frame owner.

Most user-facing drawing falls into two layers:

  • Renderer_* → frame lifecycle, sprite blit, overlay panel, pixel buffer
  • GUI_* → text, rectangles, gradients, chamfered shapes, widget drawing

Manual frame use

Use manual frame control only when you are outside the normal scene pipeline:

Renderer_BeginFrame();
GUI_DrawFilledRect(0, 0, 200, 40, 0xFF202020);
GUI_DrawTextLine(10, 10, "Hello", 0xFFFFFFFF);
Renderer_EndFrame();

Inside normal scenes, the frame pair is already handled for you.

Drawing gradients

GUI_DrawGradientRect(
    40, 40, 220, 48,
    0xFF2A4A6A,   // top
    0xFF0E2236    // bottom
);

Drawing chamfered panels / boxes

GUI_DrawChamferGradientRect(
    40, 100, 220, 60, 8,
    0xFF304A66,
    0xFF16283C
);

GUI_DrawChamferOutline(
    40, 100, 220, 60, 8, 1,
    0xFF6AA0D0
);

Drawing sprites

Renderer_DrawSprite(texture,
    0, 0, 64, 64,     // source rect
    200, 120, 128, 128, // destination rect
    0xFFFFFFFF);

If srcW and srcH are zero in entity rendering, the whole texture is used.

World-space text

If a camera is attached, GUI_DrawWorldText() automatically transforms world coordinates to screen coordinates:

GUI_DrawWorldText(
    player.x + player.w * 0.5f,
    player.y - 8.0f,
    "100 HP",
    0xFFFF4444,
    0.5f, 1.0f,
    0, 14, 0
);

This is perfect for name tags, HP labels, damage numbers, etc.


Interface widgets

InteractiveX widgets are state + drawing structs.
They are not automatic retained-mode UI. You drive them manually.

That means the pattern is usually:

  1. *_Init(...)
  2. Optional style setters
  3. In update: handle input with Button_Handle() or Slider_Handle()
  4. In render: draw with GUI_DrawButton() / GUI_DrawSlider() / etc.

Buttons

The full real pattern is:

static IXButton g_playButton;

void MyScene_OnEnter()
{
    Button_Init(&g_playButton, 40, 40, 220, 40, "Play");
    Button_SetGradients(&g_playButton,
        0xFF20324A, 0xFF122033,
        0xFF294365, 0xFF18314D,
        0xFF35608F, 0xFF214F79);
    Button_SetStroke(&g_playButton, 0xFF90C8FF, 1);
    Button_SetCornerRadius(&g_playButton, 6);
    Button_SetTooltip(&g_playButton, "Start the demo");
}

void MyScene_Update(int mouseX, int mouseY, int mouseDown, int mouseReleased)
{
    if (Button_Handle(&g_playButton, mouseX, mouseY, mouseDown, mouseReleased))
    {
        Scene_RequestChange(101);
    }
}

void MyScene_Render()
{
    GUI_DrawButton(&g_playButton);

    const char* tip = Button_GetTooltip(&g_playButton);
    if (tip)
        GUI_DrawTextLine(40, 90, tip, 0xFFFFD27F);
}

Important behavior

Button_Handle() returns 1 only when:

  • the button was pressed
  • the mouse is released
  • the pointer is still inside the button

That is real click behavior.

Sprite buttons

Button_SetSpriteStrip(&g_playButton, myTexture, 64, 32);
// maps frame 0 = normal, 1 = hover, 2 = pressed, 3 = disabled

Or explicit per state:

Button_SetSprite(&g_playButton, myTexture);
Button_SetSpriteFrame(&g_playButton, IX_BTN_NORMAL,   0,   0, 64, 32);
Button_SetSpriteFrame(&g_playButton, IX_BTN_HOVER,   64,   0, 64, 32);
Button_SetSpriteFrame(&g_playButton, IX_BTN_PRESSED, 128,  0, 64, 32);
Button_SetSpriteFrame(&g_playButton, IX_BTN_DISABLED,192,  0, 64, 32);

Labels

Labels are pure display widgets. They do not handle input.

Solid background example:

IXLabel lbl;
Label_Init(&lbl, 24, 24, 180, 28, "Score: 0000");
Label_SetBackground(&lbl, 0xCC162030, 8);
Label_SetBorder(&lbl, 0xFF4A7FB0);
Label_SetCornerRadius(&lbl, 14);
Label_SetTextColor(&lbl, 0xFFFFFFFF);

GUI_DrawLabel(&lbl);

Gradient label:

Label_SetBackgroundGradient(&lbl, 0xDD1A3A5A, 0xDD0A1828, 8);

Shadowed wrapped text:

Label_SetBounds(&lbl, 24, 24, 260, 80);
Label_SetWrap(&lbl, 1);
Label_EnableShadow(&lbl, 1);
Label_SetShadowColor(&lbl, 0xCC000000);

Sprite-backed label:

Label_SetBackgroundSprite(&lbl, texture, 0xFFFFFFFF, 4);

Panels

Panels are containers you position manually. They are useful as windows, cards and sidebars.

IXPanel panel;
Panel_Init(&panel, 16, 16, 320, 220, "Settings");

Panel_SetBodyGradient(&panel,   0xDD0A1020, 0xDD05080F);
Panel_SetHeaderGradient(&panel, 0xFF1A3A5A, 0xFF0E2236);
Panel_SetBorderStyle(&panel,    0xFF2A6A9A, 2);
Panel_SetCornerRadius(&panel,   6);
Panel_SetTitleFont(&panel, 20, 1, 1); // size, bold, align=center
Panel_SetTitleColor(&panel, 0xFFFFFFFF);

GUI_DrawPanel(&panel);

Sliders

Sliders are interactive like buttons, but they use drag state instead of click-release state.

static IXSlider g_volume;

void MyScene_OnEnter()
{
    Slider_Init(&g_volume, 40, 120, 260, 40, 0, 100, 75, "Volume");
    Slider_SetTrackStyle(&g_volume, 0x88101820, 0xCC2A7ABA, 8, 4);
    Slider_SetKnobStyle(&g_volume, 0xFFE0E8F0, 0xFFF0F8FF, 0xFFB0C8DC, 14, 18, 4);
    Slider_SetKnobStroke(&g_volume, 0xFF3A8ACA, 1);
    Slider_SetValueStyle(&g_volume, 0xFFFFD27F, 16);
}

void MyScene_Update(int mouseX, int mouseY, int mouseDown, int mouseReleased)
{
    (void)mouseReleased;
    if (Slider_Handle(&g_volume, mouseX, mouseY, mouseDown))
    {
        int value = Slider_GetValue(&g_volume);
        MediaAudio_MusicSetVolume((float)value / 100.0f);
    }
}

void MyScene_Render()
{
    GUI_DrawSlider(&g_volume);
}

Important behavior

Slider_Handle() starts dragging when the cursor is inside and the mouse is down.
While dragging, it updates from mouse X continuously. It returns 1 only if the numeric value changed.

Layout

IXLayout is a simple vertical flow helper.

This is how the shipped debug menu positions buttons:

IXLayout layout;
Layout_BeginVertical(&layout, x, y, width, height, 8);

int bx, by, bw, bh;
Layout_Next(&layout, 36, &bx, &by, &bw, &bh);
Button_SetBounds(&button1, bx, by, bw, bh);

Layout_Next(&layout, 36, &bx, &by, &bw, &bh);
Button_SetBounds(&button2, bx, by, bw, bh);

Layout_Skip(&layout, 10);

Layout_Next(&layout, 40, &bx, &by, &bw, &bh);
Slider_SetBounds(&slider, bx, by, bw, bh);

Use it whenever you want menus to adapt to resize without manual Y math.


Input and gamepad

Keyboard / mouse input

InteractiveX input is message-based.
Engine_HandleMessage() forwards Win32 events into Input_HandleMessage().

The most important distinction:

  • Input_IsKeyDown() / Input_IsMouseButtonDown() → held state
  • Input_WasKeyPressed() / Input_WasMouseButtonPressed() → edge this frame
  • Input_WasKeyReleased() / Input_WasMouseButtonReleased() → release edge this frame

Example:

if (Input_IsKeyDown('A')) player.vx = -200.0f;
if (Input_IsKeyDown('D')) player.vx =  200.0f;

if (Input_WasKeyPressed(VK_SPACE))
    Jump();

Mouse position:

int mx = Input_GetMouseX();
int my = Input_GetMouseY();

Wheel:

int delta = Input_GetMouseWheelDelta();
if (delta > 0) zoom += 0.1f;
if (delta < 0) zoom -= 0.1f;

Gamepad

Gamepad is unified across XInput and DirectInput-backed controllers.

Typical usage:

if (Gamepad_IsConnected(0))
{
    float lx = Gamepad_GetAxis(0, IX_AXIS_LX);
    float ly = Gamepad_GetAxis(0, IX_AXIS_LY);

    if (Gamepad_WasButtonPressed(0, IX_GAMEPAD_A))
        Jump();

    player.vx = lx * 220.0f;
}

Rumble:

Gamepad_SetVibration(0, 0.5f, 1.0f);

Diagnostics:

const char* name = Gamepad_GetName(0);
int backend = Gamepad_GetBackend(0);

Timer

Timer is already called by the engine.

Use it for time-based motion:

float dt = Timer_GetDeltaSeconds();
player.x += player.vx * dt;
player.y += player.vy * dt;

Useful reads:

float fps     = Timer_GetRenderFPS();
float frameMs = Timer_GetSmoothedFrameMilliseconds();
double time   = Timer_GetTotalSeconds();

If you run a fixed-step simulation inside your scene, call:

Timer_AddSimulationSteps(stepsThisFrame);

That feeds the UPS metric shown in the overlay.


Camera

The camera converts world coordinates into screen coordinates and supports follow, zoom, shake and bounds.

Real usage pattern (from the debug camera/tilemap style):

static IXCamera cam;

void InitWorld()
{
    Camera_Init(&cam);
    Camera_SetWorldBounds(&cam, 0.0f, 0.0f, 2000.0f, 1500.0f);
    Camera_SetFollowLerp(&cam, 0.10f);
    Camera_Update(&cam, 0.0f);
}

void UpdateWorld(float dt)
{
    Camera_Follow(&cam,
        player.x + player.w * 0.5f,
        player.y + player.h * 0.5f,
        dt);

    Camera_Update(&cam, dt);
}

void RenderWorld()
{
    Camera_Attach(&cam);
    Tilemap_Render(&map);
    EntityList_RenderAll(&entities);
    Camera_Detach();
}

Manual transform

int sx, sy;
Camera_WorldToScreen(&cam, player.x, player.y, &sx, &sy);
Renderer_DrawSprite(tex, 0, 0, 64, 64, sx, sy, 64, 64, 0xFFFFFFFF);

Shake

Camera_Shake(&cam, 8.0f, 0.25f);

World picking

float wx, wy;
Camera_ScreenToWorld(&cam, mouseX, mouseY, &wx, &wy);

Entities

IXEntity is the engine’s basic world object: transform, size, texture, source frame, tint, state.

Typical usage:

IXEntity player;
Entity_Init(&player, 100.0f, 200.0f, 64, 64);
Entity_SetTexture(&player, texture);
Entity_SetVelocity(&player, 80.0f, 0.0f);

float dt = Timer_GetDeltaSeconds();
Entity_Update(&player, dt);
Entity_Render(&player);

Solid color rectangles

If texture == NULL, Entity_Render() draws a solid rect using tint.

IXEntity marker;
Entity_Init(&marker, 100.0f, 100.0f, 20, 20);
Entity_SetTint(&marker, 0xFFFF0000);
Entity_Render(&marker);

Collision helpers

AABB:

if (Entity_Overlaps(&a, &b)) { ... }

Circle overlap:

if (Entity_OverlapsCircle(&a, &b)) { ... }

Point hit:

if (Entity_ContainsPoint(&buttonHitbox, wx, wy)) { ... }

Ray hit:

float tHit;
if (Entity_RayHit(&enemy, originX, originY, dirX, dirY, 1000.0f, &tHit))
{
    // hit at distance tHit
}

Entity lists

IXEntityList is fixed-capacity and does not allocate.

static IXEntity entityBuffer[128];
static IXEntityList entities;

EntityList_Init(&entities, entityBuffer, 128);

int idx = EntityList_Add(&entities);
if (idx >= 0)
{
    Entity_SetPosition(&entities.entities[idx], 100.0f, 100.0f);
    Entity_SetSize(&entities.entities[idx], 32, 32);
    Entity_SetTint(&entities.entities[idx], 0xFF00FF00);
}

EntityList_UpdateAll(&entities, Timer_GetDeltaSeconds());
EntityList_RenderAll(&entities);

Removal is swap-with-last, so order is not preserved.


Tilemaps

IXTilemap is a fixed-size grid with:

  • color-per-tile-type rendering
  • camera-aware rendering
  • solid/passable tile types
  • AABB collision resolution for entities

Setup

IXTilemap map;
Tilemap_Init(&map, 60, 24, 32, 32);

Tilemap_SetTileColor(&map, 1, 0xFF607040); // grass
Tilemap_SetTileColor(&map, 2, 0xFF808090); // stone
Tilemap_SetTileColor(&map, 3, 0xAA3868A8); // water

Tilemap_SetSolid(&map, 3, 0); // water is decorative only

Tilemap_Fill(&map, 0, 23, 60, 1, 1); // bottom row = grass
Tilemap_SetTile(&map, 5, 20, 2);

Render with camera

Camera_Attach(&cam);
Tilemap_Render(&map);
Entity_Render(&player);
Camera_Detach();

Convert world → tile

int col, row;
Tilemap_WorldToTile(&map, player.x, player.y, &col, &row);

Entity collision with solid tiles

Entity_Update(&player, dt);
Tilemap_ResolveCollision(&map, &player);

That is the intended order. The header and implementation both assume collision resolution happens after movement.

Ground probing example

This is a practical pattern taken from the tilemap debug scene:

float feetY = player.y + (float)player.h + 1.0f;
float leftX = player.x + 1.0f;
float rightX = player.x + (float)player.w - 2.0f;

int row = (int)(feetY / 32.0f);
int c0  = (int)(leftX / 32.0f);
int c1  = (int)(rightX / 32.0f);

int onGround = 0;
for (int c = c0; c <= c1; c++)
{
    if (Tilemap_IsSolid(&map, c, row))
    {
        onGround = 1;
        break;
    }
}

Animation

Animation is a spritesheet clip player.

Standalone usage

IXAnimation anim;
Animation_Init(&anim, sheetTexture, 64, 64);

Animation_AddClip(&anim, "idle",   0, 2,  8.0f, 1);
Animation_AddClip(&anim, "run",    2, 3, 12.0f, 1);
Animation_AddClip(&anim, "attack", 5, 3, 10.0f, 0);

Animation_Play(&anim, "idle");

void Update()
{
    Animation_Update(&anim, Timer_GetDeltaSeconds());
}

void Render()
{
    Animation_Render(&anim, 400, 200, 128, 128, 0xFFFFFFFF);
}

Apply to entity

This is the most useful pattern when you already use entities:

IXEntity player;
Entity_Init(&player, 500.0f, 200.0f, 128, 128);

Animation_Update(&anim, dt);
Animation_ApplyToEntity(&anim, &player);
Entity_SetTexture(&player, sheetTexture);
Entity_Render(&player);

End callback

static void OnClipEnd(IXAnimation* anim, const char* clipName)
{
    (void)clipName;
    Animation_Play(anim, "idle");
}

Animation_SetOnClipEnd(&anim, OnClipEnd);

This is especially useful for “once” animations like attack or death.


Particles

The particle system is fixed-buffer and allocation-free after setup.

Continuous emitter

#define MAX_PARTICLES 300
static IXParticle particleBuffer[MAX_PARTICLES];
static IXEmitter emitter;

Emitter_Init(&emitter, particleBuffer, MAX_PARTICLES);
Emitter_SetPosition(&emitter, 400.0f, 300.0f);
Emitter_SetSpread(&emitter, 10.0f, 10.0f);
Emitter_SetSpawnRate(&emitter, 60.0f);
Emitter_SetLifetime(&emitter, 0.4f, 1.0f);
Emitter_SetVelocity(&emitter, -40.0f, 40.0f, -120.0f, -60.0f);
Emitter_SetGravity(&emitter, 0.0f, 200.0f);
Emitter_SetSize(&emitter, 3, 8);
Emitter_SetColorOverLife(&emitter, 0xFFFF8800, 0x00FF2200);

void Update()
{
    Emitter_Update(&emitter, Timer_GetDeltaSeconds());
}

void Render()
{
    Camera_Attach(&cam);
    Emitter_Render(&emitter);
    Camera_Detach();
}

Burst

Emitter_SetSpawnRate(&emitter, 0.0f);
Emitter_Burst(&emitter, 80);

Pause / stop / reset

  • Emitter_Stop() → stop spawning, but existing particles continue until death
  • Emitter_Pause() → freeze update and spawning
  • Emitter_Reset() → kill all particles and reset the emitter

Texture particles

Emitter_SetTexture(&emitter, particleTexture, 0, 0);

If you pass NULL, particles render as solid quads.


Save / Load

The save system writes a custom .ixsav binary format with key/value entries.

Save example

SaveLoad_SetBasePath(IX_SAVE_PATH_APPDATA, "MyGame");

IXSaveWriter* w = SaveLoad_WriterCreate(1);
SaveLoad_WriterPutInt(w,   "score", 1200);
SaveLoad_WriterPutFloat(w, "speed", 3.5f);
SaveLoad_WriterPutStr(w,   "name",  "Mateus");

int ok = SaveLoad_WriterSave(w, "slot1");
SaveLoad_WriterDestroy(w);

This writes something like:

%APPDATA%\MyGame\slot1.ixsav

Load example

IXSaveReader* r = SaveLoad_ReaderOpen("slot1");
if (r)
{
    int score = SaveLoad_ReaderGetInt(r, "score");
    float speed = SaveLoad_ReaderGetFloat(r, "speed");

    char name[64];
    SaveLoad_ReaderGetStr(r, "name", name, sizeof(name));

    unsigned int version = SaveLoad_ReaderGetVersion(r);
    SaveLoad_ReaderDestroy(r);
}

File management

if (SaveLoad_FileExists("slot1"))
{
    SaveLoad_FileDelete("slot1");
}

Base path options

SaveLoad_SetBasePath(IX_SAVE_PATH_EXE, "save");
SaveLoad_SetBasePath(IX_SAVE_PATH_APPDATA, "MyGame");
SaveLoad_SetBasePath(IX_SAVE_PATH_DOCUMENTS, "MyGame\\saves");
SaveLoad_SetBasePathAbsolute("C:\\MyCustomFolder");

Audio

Audio uses miniaudio and is already initialized by Engine_Initialize().

Music

Music expects a path relative to the assets root, not the executable root.

MediaAudio_MusicPlay("music/theme.ogg", 1); // resolves to <exe>\assets\music\theme.ogg
MediaAudio_MusicSetVolume(0.75f);

if (MediaAudio_MusicIsPlaying())
{
    // ...
}

Pause / resume / stop:

MediaAudio_MusicPause();
MediaAudio_MusicResume();
MediaAudio_MusicStop();

SFX

MediaAudio_SFXPlay("sfx/jump.wav", 1.0f);
MediaAudio_SFXPlay("ui/click.wav", 0.6f);
MediaAudio_SFXSetMasterVolume(0.8f);

The engine currently uses an 8-voice SFX pool (IX_SFX_VOICES).

Diagnostics

const char* lastError = MediaAudio_GetLastError();
const char* resolved  = MediaAudio_GetLastResolvedPath();

Useful when a file is not found or an audio load fails.


Images

There are two public image APIs in the repo:

  • engine/media/image.h
  • engine/interface/image.h

Both expose IXImage and Image_* names, so do not include both in the same translation unit.

For gameplay / rendering, prefer the one already used by the debug scenes:

#include "engine/media/image.h"

Media image example

#include "engine/media/image.h"

IXImage img = {};
if (Image_LoadPNG(Renderer_GetDevice(), "assets/demo.png", &img))
{
    Renderer_DrawSprite(
        img.texture,
        0, 0, img.width, img.height,
        100, 100, img.width, img.height,
        0xFFFFFFFF
    );
}

Interface image API

The interface image module supports Image_LoadFromFile() for PNG assets. It is useful if you want a simpler generic image wrapper, but because of the symbol collision, use it carefully.


Video player

The video player decodes MPEG-1 video + MPEG audio layer 2 through pl_mpeg.

Supported format

Use .mpg / .mpeg in MPEG-1 format.

Suggested ffmpeg conversion from the source comments:

ffmpeg -i input.mp4 -c:v mpeg1video -c:a mp2 -q:v 5 output.mpg

Open / update / render pattern

if (VideoPlayer_Open("assets/video/test.mpg"))
{
    VideoPlayer_SetLoop(0);
    VideoPlayer_Play();
}

void Update()
{
    if (VideoPlayer_IsPlaying())
        VideoPlayer_Update(Timer_GetDeltaSeconds());
}

void Render()
{
    VideoPlayer_RenderFit(392, 4, 800, 600, 0xFF000000);
}

Controls

VideoPlayer_Play();
VideoPlayer_Pause();
VideoPlayer_Stop();
VideoPlayer_SetLoop(1);
VideoPlayer_SetVolume(0.8f);

State queries

if (VideoPlayer_IsFinished()) { ... }

float pos = VideoPlayer_GetPosition();
float dur = VideoPlayer_GetDuration();
int w = VideoPlayer_GetVideoWidth();
int h = VideoPlayer_GetVideoHeight();

Blocking playback

For simple intro/cutscene cases without a normal game loop:

VideoPlayer_PlayBlocking(hWnd, "assets/video/intro.mpg", 0, 1);

Arguments:

  • loop
  • hideOverlay

ESC, Enter, Space, and mouse click exit blocking playback.

Important path detail

Unlike the audio system, VideoPlayer_Open() takes the path directly and does not call Assets_BuildPath() for you.
So pass the full asset-relative path yourself, e.g. "assets/video/test.mpg".


Splash screen

Splash is a self-contained blocking screen used before the main loop.

Quick use

Splashscreen_Show(hWnd, NULL, 3.0f);

If the image path is NULL, the engine tries:

assets/splash/bg.png

Full control

Splashscreen_ShowEx(
    hWnd,
    "assets/logo.png",
    4.0f,         // total duration
    0.6f,         // fade in
    0.8f,         // fade out
    0xFF000000,   // background color
    1             // skipFadeOnExit
);

Skip inputs: ESC, Enter, Space, mouse click.


Video options / window control

The video module controls:

  • fullscreen borderless
  • windowed resolution
  • vsync
  • resize lock
  • minimum size
  • display mode enumeration

Typical setup

Video_Initialize(hWnd);
Video_SetResolution(1280, 720);
Video_SetFullscreen(0);
Video_SetVSync(1);
Video_LockResize(1);
Video_SetMinSize(640, 480);
Video_Apply();

Runtime changes

Video_SetFullscreen(1);
Video_Apply();

Available modes

IXVideoMode modes[64];
int count = Video_GetAvailableModes(modes, 64);
for (int i = 0; i < count; i++)
{
    // modes[i].width / height / refreshRate
}

Important rule

Video_Apply() can reset the D3D device.
Call it outside any Renderer_BeginFrame() / Renderer_EndFrame() pair.


Transitions

Transitions are engine-managed scene overlays.

Basic usage

Transition_FadeBlack(IX_SCENE_MENU, 0.4f);
Transition_FadeWhite(IX_SCENE_MENU, 0.4f);

Full control

Transition_Request(IX_SCENE_MENU, IX_TRANSITION_WIPE_H, 0.5f, 0x000000);

Available types

  • IX_TRANSITION_FADE
  • IX_TRANSITION_WIPE_H
  • IX_TRANSITION_WIPE_V

Blocking input while active

if (Transition_IsActive())
    return;

The engine already updates and renders transition overlays internally.


Random and Math2D

Both modules are header-only.

RNG

IXRng rng;
Rng_Seed(&rng, 12345);

int number = Rng_Int(&rng, 1, 6);
float v = Rng_Float(&rng, -1.0f, 1.0f);

if (Rng_Chance(&rng, 0.25f))
{
    // 25% chance
}

Vec2 basics

IXVec2 a = Vec2(10.0f, 20.0f);
IXVec2 b = Vec2(40.0f, 60.0f);

IXVec2 delta = Vec2_Sub(b, a);
IXVec2 dir   = Vec2_Normalize(delta);
float dist   = Vec2_Length(delta);

Angles

float radians = Vec2_Angle(delta);
IXVec2 offset = Vec2_FromAngle(IX_DEG2RAD(45.0f), 100.0f);

Overlap helpers

if (IX_OverlapAABB(ax, ay, aw, ah, bx, by, bw, bh)) { ... }
if (IX_OverlapCircle(ax, ay, ar, bx, by, br)) { ... }
if (IX_PointInAABB(px, py, rx, ry, rw, rh)) { ... }
if (IX_PointInCircle(px, py, cx, cy, cr)) { ... }

Ray vs AABB

float tHit;
if (IX_RayAABB(ox, oy, dx, dy, 1000.0f, rx, ry, rw, rh, &tHit))
{
    // hit
}

Effects / fire system

The engine/effects module exposes a configurable fire effect.

High-level wrapper

FireSettings settings = {};
settings.width = 320;
settings.height = 168;
settings.paletteSize = 36;
settings.maxDecay = 3;
settings.windStrength = 0;
settings.baseIntensity = 255;

Effect_InitializeFire(settings);
Effect_SetFireSpeed(16);
Effect_UpdateFire();

const unsigned int* pixels = Effect_GetFireBuffer();
int w = Effect_GetFireWidth();
int h = Effect_GetFireHeight();

Renderer_DrawPixelBuffer(pixels, w, h, 0, 0, 640, 336);

Lower-level fire module

If you want the raw fire module directly:

Fire_Initialize(320, 168, 36, 3, 0, 255);
Fire_Update();
const unsigned int* pixels = Fire_GetBuffer();

Important caveats discovered in the code

These are source-accurate notes that matter when you build on r0.2.

1. onExit exists in scenes, but is not currently called

IXSceneCallbacks contains onExit, but scene.cpp never invokes it when switching scenes.

So today:

  • onEnter is used
  • onExit is defined but not wired yet

If you need cleanup, do it another way for now.

2. Scene render already owns the frame

Inside normal scenes, Scene_Render() calls Renderer_BeginFrame() and Renderer_EndFrame() for you.

Do not begin/end a second frame inside your scene’s render() callback.

3. Audio paths and video paths are not handled the same way

  • MediaAudio_MusicPlay("music/theme.ogg", ...) → path is resolved through the assets system
  • VideoPlayer_Open("assets/video/test.mpg") → path is used directly

So audio takes a path relative to assets, while video expects the actual file path string you provide.

4. Two public image modules share the same names

Both engine/media/image.h and engine/interface/image.h define:

  • IXImage
  • Image_Initialize
  • Image_Shutdown
  • Image_Release
  • Image_GetLastError

Do not include both together.

5. Buttons and sliders are manual

The engine does not automatically update widgets for you.

You must:

  • call Button_Handle() / Slider_Handle() in update
  • call GUI_DrawButton() / GUI_DrawSlider() in render

6. Tilemap collision is post-move resolution

The intended order is:

Entity_Update(&player, dt);
Tilemap_ResolveCollision(&map, &player);

7. GUI_DrawTextLine uses a default argument

GUI_DrawTextLine(int x, int y, const char* text, unsigned int color = 0xFFFFFFFF);

That default argument means this header is effectively written for C++ compilation, which matches the rest of the project.


Public API reference

Below is a straight source-based signature index of the public headers shipped in r0.2.

engine/core/engine.h

bool Engine_Initialize(HWND hWnd);
void Engine_Shutdown();
void Engine_HandleMessage(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void Engine_RunFrame();

engine/core/video.h

void Video_Initialize(HWND hWnd);
void Video_SetFullscreen(int fullscreen);
void Video_SetResolution(int width, int height);
void Video_SetVSync(int vsync);
void Video_LockResize(int locked);
void Video_SetMinSize(int minWidth, int minHeight);
int Video_Apply();
int Video_IsFullscreen();
int Video_IsVSync();
int Video_IsResizeLocked();
void Video_GetResolution(int* width, int* height);
int Video_GetAvailableModes(IXVideoMode* outModes, int maxCount);
void Video_HandleGetMinMaxInfo(LPARAM lParam);
void Video_HandleSizing(WPARAM wParam, LPARAM lParam);

engine/core/splashscreen.h

void Splashscreen_Show(HWND hWnd, const char* imagePath, float durationSeconds);
void Splashscreen_ShowEx(HWND hWnd, const char* imagePath, float durationSeconds, float fadeInSeconds, float fadeOutSeconds, unsigned int bgColor, int skipFadeOnExit);

engine/core/scene.h

int  Scene_Register(int sceneId, IXSceneCallbacks callbacks);
void Scene_RegisterDebugScenes();
void Scene_SetCurrent(int sceneId);
int  Scene_GetCurrent();
void Scene_RequestChange(int sceneId);
int  Scene_HasPendingChange();
int  Scene_ConsumePendingChange();
void Scene_Initialize();
void Scene_OnEnterCurrent();
void Scene_InvalidateLayout();
void Scene_HandleMouseMove(int x, int y);
void Scene_BeginMouseDrag(int x, int y);
void Scene_EndMouseDrag();
void Scene_HandleKeyDown(WPARAM key);
void Scene_Update();
void Scene_Render();

engine/core/input.h

void Input_Initialize();
void Input_BeginFrame();
void Input_HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam);
int Input_GetMouseX();
int Input_GetMouseY();
int Input_GetMouseWheelDelta();
int Input_IsMouseButtonDown(int button);
int Input_WasMouseButtonPressed(int button);
int Input_WasMouseButtonReleased(int button);
int Input_IsKeyDown(int vk);
int Input_WasKeyPressed(int vk);
int Input_WasKeyReleased(int vk);
int Input_GetLastPressedKey();
int Input_GetLastReleasedKey();
const char* Input_KeyToString(int vk);

engine/core/gamepad.h

void Gamepad_Initialize();
void Gamepad_Update();
void Gamepad_Shutdown();
int   Gamepad_IsConnected(int index);
int   Gamepad_IsButtonDown     (int index, unsigned int button);
int   Gamepad_WasButtonPressed (int index, unsigned int button);
int   Gamepad_WasButtonReleased(int index, unsigned int button);
float Gamepad_GetAxis          (int index, int axis);
int   Gamepad_IsTriggerDown    (int index, int axis);
int   Gamepad_WasTriggerPressed (int index, int axis);
int   Gamepad_WasTriggerReleased(int index, int axis);
void  Gamepad_SetVibration(int index, float leftMotor, float rightMotor);
void  Gamepad_StopVibration(int index);
void  Gamepad_SetDeadZone(int index, float deadZone);
float Gamepad_GetDeadZone(int index);
int         Gamepad_GetConnectedCount();
const char* Gamepad_ButtonToString(unsigned int button);
const char* Gamepad_AxisToString(int axis);

engine/core/timer.h

void Timer_Initialize();
void Timer_BeginFrame();
void Timer_EndFrame();
void Timer_AddSimulationSteps(unsigned int steps);
float Timer_GetDeltaSeconds();
float Timer_GetDeltaMilliseconds();
float Timer_GetSmoothedFrameMilliseconds();
float Timer_GetRenderFPS();
float Timer_GetSimUPS();
double Timer_GetTotalSeconds();
unsigned int Timer_GetFrameCount();
unsigned int Timer_GetFrameSimulationSteps();

engine/core/camera.h

void Camera_Init(IXCamera* cam);
void Camera_SetPosition(IXCamera* cam, float x, float y);
void Camera_SetZoom(IXCamera* cam, float zoom);
void Camera_SetWorldBounds(IXCamera* cam, float x, float y, float w, float h);
void Camera_SetFollowLerp(IXCamera* cam, float lerp);
void Camera_Follow(IXCamera* cam, float targetX, float targetY, float deltaSeconds);
void Camera_CenterOn(IXCamera* cam, float targetX, float targetY);
void Camera_Update(IXCamera* cam, float deltaSeconds);
void Camera_Shake(IXCamera* cam, float intensity, float duration);
void Camera_WorldToScreen(const IXCamera* cam, float worldX, float worldY, int* screenX, int* screenY);
void Camera_ScreenToWorld(const IXCamera* cam, int screenX, int screenY, float* worldX, float* worldY);
int Camera_IsVisible(const IXCamera* cam, float worldX, float worldY, int w, int h);
void Camera_Attach(IXCamera* cam);
void Camera_Detach();
IXCamera* Camera_GetActive();

engine/core/entity.h

void Entity_Init(IXEntity* e, float x, float y, int w, int h);
void Entity_SetTexture(IXEntity* e, IDirect3DTexture9* texture);
void Entity_SetSpriteFrame(IXEntity* e, int srcX, int srcY, int srcW, int srcH);
void Entity_SetPosition(IXEntity* e, float x, float y);
void Entity_SetVelocity(IXEntity* e, float vx, float vy);
void Entity_SetSize(IXEntity* e, int w, int h);
void Entity_SetTint(IXEntity* e, unsigned int tint);
void Entity_SetActive(IXEntity* e, int active);
void Entity_SetVisible(IXEntity* e, int visible);
void Entity_GetBounds(const IXEntity* e, int* x, int* y, int* w, int* h);
void Entity_Update(IXEntity* e, float deltaSeconds);
int  Entity_Overlaps(const IXEntity* a, const IXEntity* b);
int  Entity_OverlapsCircle(const IXEntity* a, const IXEntity* b);
int  Entity_ContainsPoint(const IXEntity* e, float px, float py);
int  Entity_RayHit(const IXEntity* e, float ox, float oy, float dx, float dy, float maxLen, float* tHit);
void Entity_Render(const IXEntity* e);
void EntityList_Init(IXEntityList* list, IXEntity* buffer, int capacity);
int  EntityList_Add(IXEntityList* list);
void EntityList_Remove(IXEntityList* list, int index);
void EntityList_Clear(IXEntityList* list);
void EntityList_UpdateAll(IXEntityList* list, float deltaSeconds);
void EntityList_RenderAll(const IXEntityList* list);
int  EntityList_GetCount(const IXEntityList* list);

engine/core/tilemap.h

void Tilemap_Init(IXTilemap* map, int cols, int rows, int tileW, int tileH);
void Tilemap_SetTileColor(IXTilemap* map, unsigned char type, unsigned int color);
void Tilemap_SetSolid(IXTilemap* map, unsigned char type, int solid);
void          Tilemap_SetTile(IXTilemap* map, int col, int row, unsigned char type);
unsigned char Tilemap_GetTile(const IXTilemap* map, int col, int row);
void Tilemap_Fill(IXTilemap* map, int col, int row, int cols, int rows, unsigned char type);
void Tilemap_Clear(IXTilemap* map);
int Tilemap_GetWorldWidth(const IXTilemap* map);
int Tilemap_GetWorldHeight(const IXTilemap* map);
void Tilemap_WorldToTile(const IXTilemap* map, float worldX, float worldY, int* col, int* row);
int Tilemap_IsSolid(const IXTilemap* map, int col, int row);
int Tilemap_OverlapsSolid(const IXTilemap* map, float x, float y, int w, int h);
void Tilemap_Render(const IXTilemap* map);
void Tilemap_ResolveCollision(const IXTilemap* map, IXEntity* entity);

engine/core/animation.h

void Animation_Init(IXAnimation* anim, struct IDirect3DTexture9* texture, int frameW, int frameH);
void Animation_SetTexture(IXAnimation* anim, struct IDirect3DTexture9* texture);
int Animation_AddClip(IXAnimation* anim, const char*  name, int          firstFrame, int          frameCount, float        fps, int          loop);
int  Animation_Play(IXAnimation* anim, const char* clipName);
void Animation_Restart(IXAnimation* anim);
void Animation_Pause(IXAnimation* anim);
void Animation_Resume(IXAnimation* anim);
void Animation_SetOnClipEnd(IXAnimation* anim, IXAnimationCallback cb);
const char* Animation_GetCurrentClipName(const IXAnimation* anim);
int         Animation_GetCurrentFrame(const IXAnimation* anim);
int         Animation_IsPaused(const IXAnimation* anim);
void Animation_Update(IXAnimation* anim, float deltaSeconds);
void Animation_Render(const IXAnimation* anim, int dstX, int dstY, int dstW, int dstH, unsigned int tint);
void Animation_ApplyToEntity(const IXAnimation* anim, struct IXEntity* entity);

engine/core/particles.h

void Emitter_Init(IXEmitter* e, IXParticle* buffer, int capacity);
void Emitter_SetPosition(IXEmitter* e, float x, float y);
void Emitter_SetSpread(IXEmitter* e, float spreadX, float spreadY);
void Emitter_SetSpawnRate(IXEmitter* e, float particlesPerSecond);
void Emitter_SetLifetime(IXEmitter* e, float minSec, float maxSec);
void Emitter_SetVelocity(IXEmitter* e, float vxMin, float vxMax, float vyMin, float vyMax);
void Emitter_SetGravity(IXEmitter* e, float ax, float ay);
void Emitter_SetSize(IXEmitter* e, int sizeStart, int sizeEnd);
void Emitter_SetColorOverLife(IXEmitter* e, unsigned int colorStart, unsigned int colorEnd);
void Emitter_SetTexture(IXEmitter* e, IDirect3DTexture9* texture, int frameW, int frameH);
void Emitter_Burst(IXEmitter* e, int count);
void Emitter_Stop(IXEmitter* e);
void Emitter_Reset(IXEmitter* e);
void Emitter_Pause(IXEmitter* e);
void Emitter_Resume(IXEmitter* e);
void Emitter_Update(IXEmitter* e, float deltaSeconds);
void Emitter_Render(const IXEmitter* e);
int Emitter_GetActiveCount(const IXEmitter* e);

engine/core/saveload.h

void SaveLoad_SetBasePath(int root, const char* subdir);
void SaveLoad_SetBasePathAbsolute(const char* absolutePath);
const char* SaveLoad_GetBasePath();
IXSaveWriter* SaveLoad_WriterCreate(unsigned int version);
void SaveLoad_WriterPutBytes(IXSaveWriter* w, const char* key, const void* data, unsigned int len);
void SaveLoad_WriterPutInt  (IXSaveWriter* w, const char* key, int value);
void SaveLoad_WriterPutFloat(IXSaveWriter* w, const char* key, float value);
void SaveLoad_WriterPutStr  (IXSaveWriter* w, const char* key, const char* str);
int  SaveLoad_WriterSave(IXSaveWriter* w, const char* filename);
void SaveLoad_WriterDestroy(IXSaveWriter* w);
IXSaveReader* SaveLoad_ReaderOpen(const char* filename);
unsigned int SaveLoad_ReaderGetVersion(IXSaveReader* r);
unsigned int SaveLoad_ReaderGetCount(IXSaveReader* r);
const void*  SaveLoad_ReaderGetBytes(IXSaveReader* r, const char* key, unsigned int* outLen);
int         SaveLoad_ReaderGetInt  (IXSaveReader* r, const char* key);
float       SaveLoad_ReaderGetFloat(IXSaveReader* r, const char* key);
int         SaveLoad_ReaderGetStr  (IXSaveReader* r, const char* key, char* buf, unsigned int bufLen);
void SaveLoad_ReaderDestroy(IXSaveReader* r);
int SaveLoad_FileExists(const char* filename);
int SaveLoad_FileDelete(const char* filename);

engine/core/random.h

static inline void Rng_Seed(IXRng* rng, unsigned int seed);
static inline unsigned int Rng_NextU32(IXRng* rng);
static inline float Rng_NextFloat01(IXRng* rng);
static inline float Rng_Float(IXRng* rng, float lo, float hi);
static inline int Rng_Int(IXRng* rng, int lo, int hi);
static inline int Rng_Chance(IXRng* rng, float p);
static inline int Rng_Bool(IXRng* rng);
static inline int Rng_Sign(IXRng* rng);
static inline float Rng_FloatSigned(IXRng* rng);
static inline int Rng_Index(IXRng* rng, int count);
static inline void Rng_ShuffleInts(IXRng* rng, int* arr, int count);
static inline void Rng_SeedFromTime(IXRng* rng);
Rng_Seed(rng, (unsigned int)time(NULL));

engine/core/math2d.h

static inline IXVec2 Vec2(float x, float y);
static inline IXVec2 Vec2_Zero();
static inline IXVec2 Vec2_One();
static inline IXVec2 Vec2_Up();
static inline IXVec2 Vec2_Down();
static inline IXVec2 Vec2_Left();
static inline IXVec2 Vec2_Right();
static inline IXVec2 Vec2_Add(IXVec2 a, IXVec2 b);
static inline IXVec2 Vec2_Sub(IXVec2 a, IXVec2 b);
static inline IXVec2 Vec2_Scale(IXVec2 v, float s);
static inline IXVec2 Vec2_Neg(IXVec2 v);
static inline IXVec2 Vec2_Mul(IXVec2 a, IXVec2 b);
static inline float  Vec2_LengthSq(IXVec2 v);
static inline float  Vec2_Length(IXVec2 v);
static inline IXVec2 Vec2_Normalize(IXVec2 v);
static inline float  Vec2_Dot(IXVec2 a, IXVec2 b);
static inline float  Vec2_Cross(IXVec2 a, IXVec2 b);
static inline float  Vec2_Dist(IXVec2 a, IXVec2 b);
static inline float  Vec2_DistSq(IXVec2 a, IXVec2 b);
static inline float  Vec2_Angle(IXVec2 v);
static inline float  Vec2_AngleBetween(IXVec2 a, IXVec2 b);
static inline IXVec2 Vec2_FromAngle(float radians, float length);
static inline IXVec2 Vec2_Rotate(IXVec2 v, float radians);
static inline IXVec2 Vec2_RotateAround(IXVec2 point, IXVec2 pivot, float radians);
static inline IXVec2 Vec2_Reflect(IXVec2 v, IXVec2 normal);
static inline IXVec2 Vec2_Perp(IXVec2 v);
static inline IXVec2 Vec2_Lerp(IXVec2 a, IXVec2 b, float t);
static inline IXVec2 Vec2_MoveTowards(IXVec2 current, IXVec2 target, float maxDist);
static inline IXVec2 Vec2_ClampLength(IXVec2 v, float maxLength);
static inline float IX_Lerp(float a, float b, float t);
static inline float IX_Clamp(float v, float lo, float hi);
static inline float IX_Clamp01(float v);
static inline float IX_Abs(float v);
static inline float IX_Sign(float v);
static inline float IX_Min(float a, float b);
static inline float IX_Max(float a, float b);
static inline float IX_Min3(float a, float b, float c);
static inline float IX_Max3(float a, float b, float c);
static inline float IX_Wrap(float v, float lo, float hi);
static inline float IX_WrapAngle(float radians);
static inline float IX_SmoothStep(float t);
static inline float IX_SmoothLerp(float a, float b, float t);
static inline float IX_MoveTowards(float v, float target, float maxStep);
static inline int IX_OverlapAABB(float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh);
static inline int IX_OverlapCircle(float ax, float ay, float ar, float bx, float by, float br);
static inline int IX_PointInAABB(float px, float py, float rx, float ry, float rw, float rh);
static inline int IX_PointInCircle(float px, float py, float cx, float cy, float cr);
static inline int IX_RayAABB(float ox, float oy, float dx, float dy, float maxLen, float rx, float ry, float rw, float rh, float* tHit);

engine/core/assets.h

int Assets_Initialize();
void Assets_Shutdown();
void Assets_SetBasePath(const char* basePath);
const char* Assets_GetBasePath();
const char* Assets_GetAssetsRoot();
const char* Assets_GetImagesRoot();
const char* Assets_GetAudioRoot();
int Assets_BuildPath(const char* relativePath, char* outPath, int outPathSize);
int Assets_BuildImagePath(const char* fileName, char* outPath, int outPathSize);
int Assets_BuildAudioPath(const char* fileName, char* outPath, int outPathSize);
int Assets_FileExists(const char* fullPath);
int Assets_ImageExists(const char* fileName);
int Assets_AudioExists(const char* fileName);

engine/core/transition.h

void Transition_Request(int toScene, int type, float durationSeconds, unsigned int color);
void Transition_FadeBlack(int toScene, float durationSeconds);
void Transition_FadeWhite(int toScene, float durationSeconds);
int  Transition_IsActive();
void Transition_Update(float deltaSeconds);
void Transition_DrawOverlay(int screenWidth, int screenHeight);

engine/renderer.h

bool Renderer_Initialize(HWND hWnd);
void Renderer_HandleResize(int width, int height);
void Renderer_Cleanup();
void              Renderer_GetClientSize(int* width, int* height);
void              Renderer_GetOverlayContentRect(int* x, int* y, int* width, int* height);
void              Renderer_GetOverlayButtonsRect(int* x, int* y, int* width, int* height);
IDirect3DDevice9* Renderer_GetDevice();
void Renderer_SetDisplayImage(IDirect3DTexture9* texture, int width, int height);
void Renderer_ClearDisplayImage();
bool Renderer_LoadBackgroundImage(const char* path);
void Renderer_ClearBackgroundImage();
void Renderer_BeginFrame();
void Renderer_DrawPixelBuffer(const unsigned int* pixels, int srcWidth,  int srcHeight, int dstX,      int dstY, int dstWidth,  int dstHeight);
void Renderer_DrawSprite(IDirect3DTexture9* texture, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY, int dstW, int dstH, unsigned int tint);
void Renderer_EndFrame();
void Renderer_SetOverlayText(const char* title, const char* subtitle, const char* statusLine);
void Renderer_SetOverlayMetrics(float renderFps, float frameMs, float simUps);
void Renderer_SetControlButton(const IXButton* button);
void Renderer_SetControlButtons(const IXButton** buttons, int count);
void Renderer_SetControlSliders(const IXSlider** sliders, int count);
void Renderer_SetDisplayOptions(int showFPS, int showHelp, int drawBackgroundGrid);
void Renderer_SetBackgroundColor(unsigned int argb);
void Renderer_SetHelpText(const char* helpText);
void Renderer_SetOverlayEnabled(int enabled);
void Renderer_SetVSync(int vsync);

engine/gui.h

bool GUI_Initialize();
bool GUI_InternalCreate(IDirect3DDevice9* device);
void GUI_BeginFrame();
void GUI_EndFrame();
void GUI_Cleanup();
void GUI_DrawTextEx(int x, int y, int width, int height, const char* text, unsigned int color, int alignment, int fontSize, int bold, int wrap);
void GUI_DrawWorldText(float worldX, float worldY, const char* text, unsigned int color, float anchorX, float anchorY, int alignment, int fontSize, int bold);
void GUI_DrawFilledRect(int x, int y, int width, int height, unsigned int color);
void GUI_DrawOutlinedRect(int x, int y, int width, int height, unsigned int color);
void GUI_DrawGradientRect(int x, int y, int width, int height, unsigned int colorTop, unsigned int colorBottom);
void GUI_DrawChamferRect(int x, int y, int width, int height, int radius, unsigned int color);
void GUI_DrawChamferGradientRect(int x, int y, int width, int height, int radius, unsigned int colorTop, unsigned int colorBottom);
void GUI_DrawChamferOutline(int x, int y, int width, int height, int radius, int strokeWidth, unsigned int color);
void GUI_DrawSprite(IDirect3DTexture9* texture, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY, int dstW, int dstH, unsigned int tint);
void GUI_DrawButton(const IXButton* button);
void GUI_DrawSlider(const IXSlider* slider);
void GUI_DrawPanel(const IXPanel* panel);
void GUI_DrawLabel(const IXLabel* label);

engine/interface/button.h

void Button_Init(IXButton* button, int x, int y, int width, int height, const char* text);
void Button_SetText(IXButton* button, const char* text);
void Button_SetTooltip(IXButton* button, const char* tooltip);
void Button_SetBounds(IXButton* button, int x, int y, int width, int height);
void Button_SetCornerRadius(IXButton* button, int radius);
void Button_SetEnabled(IXButton* button, int enabled);
void Button_SetFont(IXButton* button, int fontSize, int bold);
void Button_SetTextPressOffset(IXButton* button, int dx, int dy);
void Button_SetColors(IXButton* button, unsigned int normal, unsigned int hover, unsigned int pressed);
void Button_SetGradients(IXButton* button, unsigned int topNormal,  unsigned int botNormal, unsigned int topHover,   unsigned int botHover, unsigned int topPressed, unsigned int botPressed);
void Button_SetStroke(IXButton* button, unsigned int color, int width);
void Button_SetTextColors(IXButton* button, unsigned int normal, unsigned int hover, unsigned int pressed);
void Button_SetSprite(IXButton* button, IDirect3DTexture9* texture);
void Button_SetSpriteFrame(IXButton* button, int state, int srcX, int srcY, int srcW, int srcH);
void Button_SetSpriteStrip(IXButton* button, IDirect3DTexture9* texture, int frameW, int frameH);
int               Button_IsInside(const IXButton* button, int mouseX, int mouseY);
int               Button_Handle(IXButton* button, int mouseX, int mouseY, int mouseDown, int mouseReleased);
const IXButtonStyle* Button_GetCurrentStyle(const IXButton* button);

engine/interface/label.h

void Label_Init(IXLabel* label, int x, int y, int width, int height, const char* text);
void Label_SetText(IXLabel* label, const char* text);
void Label_SetBounds(IXLabel* label, int x, int y, int width, int height);
void Label_SetFontSize(IXLabel* label, int fontSize);
void Label_SetBold(IXLabel* label, int bold);
void Label_SetAlignment(IXLabel* label, int alignment);
void Label_SetTextColor(IXLabel* label, unsigned int color);
void Label_EnableShadow(IXLabel* label, int enabled);
void Label_SetShadowColor(IXLabel* label, unsigned int color);
void Label_SetWrap(IXLabel* label, int wrap);
void Label_SetBackground(IXLabel* label, unsigned int color, int padding);
void Label_SetBackgroundGradient(IXLabel* label, unsigned int top, unsigned int bottom, int padding);
void Label_SetBackgroundSprite(IXLabel* label, IDirect3DTexture9* texture, unsigned int tint, int padding);
void Label_SetBorder(IXLabel* label, unsigned int color);
void Label_SetStrokeWidth(IXLabel* label, int width);
void Label_SetCornerRadius(IXLabel* label, int radius);
int Label_IsInside(const IXLabel* label, int pointX, int pointY);

engine/interface/panel.h

void Panel_Init(IXPanel* panel, int x, int y, int width, int height, const char* title);
void Panel_SetTitle(IXPanel* panel, const char* title);
void Panel_SetBounds(IXPanel* panel, int x, int y, int width, int height);
int  Panel_IsInside(const IXPanel* panel, int pointX, int pointY);
void Panel_SetBackground(IXPanel* panel, unsigned int color);
void Panel_SetBodyGradient(IXPanel* panel, unsigned int top, unsigned int bottom);
void Panel_SetBodySprite(IXPanel* panel, IDirect3DTexture9* texture, unsigned int tint);
void Panel_SetHeaderColor(IXPanel* panel, unsigned int color);
void Panel_SetHeaderGradient(IXPanel* panel, unsigned int top, unsigned int bottom);
void Panel_SetHeaderHeight(IXPanel* panel, int height);
void Panel_SetTitleFont(IXPanel* panel, int fontSize, int bold, int alignment);
void Panel_SetTitleColor(IXPanel* panel, unsigned int color);
void Panel_SetBorderStyle(IXPanel* panel, unsigned int color, int strokeWidth);
void Panel_SetCornerRadius(IXPanel* panel, int radius);

engine/interface/slider.h

void Slider_Init(IXSlider* slider, int x, int y, int width, int height, int minValue, int maxValue, int value, const char* label);
void Slider_SetLabel(IXSlider* slider, const char* label);
void Slider_SetBounds(IXSlider* slider, int x, int y, int width, int height);
void Slider_SetValue(IXSlider* slider, int value);
int  Slider_GetValue(const IXSlider* slider);
int  Slider_IsInside(const IXSlider* slider, int mouseX, int mouseY);
int  Slider_Handle(IXSlider* slider, int mouseX, int mouseY, int mouseDown);
void Slider_SetTrackStyle(IXSlider* slider, unsigned int trackColor, unsigned int fillColor, int height, int radius);
void Slider_SetTrackInset(IXSlider* slider, unsigned int insetColor);
void Slider_SetKnobStyle(IXSlider* slider, unsigned int color, unsigned int gradTop, unsigned int gradBottom, int width, int height, int radius);
void Slider_SetKnobStroke(IXSlider* slider, unsigned int color, int strokeWidth);
void Slider_SetKnobSprite(IXSlider* slider, IDirect3DTexture9* texture);
void Slider_SetValueStyle(IXSlider* slider, unsigned int valueColor, int valueFontSize);
void Slider_SetLabelFontSize(IXSlider* slider, int fontSize);
void Slider_ShowValue(IXSlider* slider, int show);

engine/interface/layout.h

void Layout_BeginVertical(IXLayout* layout, int x, int y, int width, int height, int spacing);
void Layout_SetPadding(IXLayout* layout, int padding);
void Layout_Reset(IXLayout* layout);
void Layout_GetContentRect(const IXLayout* layout, int* x, int* y, int* width, int* height);
void Layout_Next(IXLayout* layout, int itemHeight, int* x, int* y, int* width, int* height);
void Layout_Skip(IXLayout* layout, int amount);
int  Layout_GetCursorY(const IXLayout* layout);

engine/interface/image.h

bool Image_Initialize();
void Image_Shutdown();
bool Image_LoadFromFile(IDirect3DDevice9* device, const char* path, IXImage* outImage);
void Image_Release(IXImage* image);
const char* Image_GetLastError();

engine/media/audio.h

int  MediaAudio_Initialize(HWND hWnd);
void MediaAudio_Shutdown();
int  MediaAudio_MusicPlay(const char* relativeAssetPath, int loop);
void MediaAudio_MusicStop();
void MediaAudio_MusicPause();
void MediaAudio_MusicResume();
float MediaAudio_MusicGetVolume();
int  MediaAudio_MusicIsPlaying();
int  MediaAudio_MusicIsPaused();
int  MediaAudio_SFXPlay(const char* relativeAssetPath, float volume);
float MediaAudio_SFXGetMasterVolume();
const char* MediaAudio_GetLastError();
const char* MediaAudio_GetLastResolvedPath();
int  MediaAudio_PlayFile(const char* relativeAssetPath, int loop);
int  MediaAudio_PlayDemoWAV(int loop);
void MediaAudio_Stop();
void MediaAudio_Pause();
void MediaAudio_Resume();
int  MediaAudio_IsPaused();
int  MediaAudio_HasActiveFile();

engine/media/image.h

int  Image_Initialize();
void Image_Shutdown();
int  Image_LoadPNG(IDirect3DDevice9* device, const char* path, IXImage* outImage);
void Image_Release(IXImage* image);
int  Image_IsLoaded(const IXImage* image);
int  Image_GetWidth(const IXImage* image);
int  Image_GetHeight(const IXImage* image);
const char* Image_GetPath(const IXImage* image);
const char* Image_GetLastError();

engine/media/video_player.h

int  VideoPlayer_Open(const char* path);
void VideoPlayer_Close();
void VideoPlayer_Play();
void VideoPlayer_Pause();
void VideoPlayer_SetLoop(int loop);
void VideoPlayer_Update(float deltaSeconds);
void VideoPlayer_Render(int x, int y, int w, int h);
void VideoPlayer_RenderFit(int x, int y, int w, int h, unsigned int fillColor);
int   VideoPlayer_IsOpen();
int   VideoPlayer_IsPlaying();
int   VideoPlayer_IsPaused();
int   VideoPlayer_IsFinished();
int   VideoPlayer_GetVideoWidth();
int   VideoPlayer_GetVideoHeight();
float VideoPlayer_GetVolume();
void VideoPlayer_PlayBlocking(HWND hWnd, const char* path, int loop, int hideOverlay);
const char* VideoPlayer_GetLastError();

engine/effects/effect.h

void Effect_InitializeFire(const FireSettings& settings);
void Effect_ResetFire();
void Effect_UpdateFire();
void Effect_SetFireSpeed(int milliseconds);
int  Effect_GetFireSpeed();
void Effect_SetFireWind(int windStrength);
int  Effect_GetFireWind();
void Effect_SetFireBaseIntensity(int intensity);
int  Effect_GetFireBaseIntensity();
void Effect_SetFireDecay(int maxDecay);
int  Effect_GetFireDecay();
void Effect_SetFireBlur(int blurAmount);
int  Effect_GetFireBlur();
void Effect_SetFireSize(int width, int height);
const FireSettings& Effect_GetFireSettings();
const unsigned int* Effect_GetFireBuffer();
int Effect_GetFireWidth();
int Effect_GetFireHeight();

engine/effects/fire.h

void Fire_Initialize(int width, int height, int paletteSize, int maxDecay, int windStrength, int baseIntensity);
void Fire_Update();
void Fire_SetWind(int windStrength);
void Fire_SetBaseIntensity(int intensity);
void Fire_SetDecay(int maxDecay);
void Fire_SetBlur(int blurAmount);
const unsigned int* Fire_GetBuffer();
int Fire_GetWidth();
int Fire_GetHeight();