๐ŸŽฎ Game Development

Engine Architecture

What Is a Game Engine?

"A game engine is a framework that abstracts platform details and provides reusable systems so you can focus on gameplay, not plumbing." โ€” Jason Gregory

Engine โ‰  Game. The engine is the runtime + tooling; the game is the content + logic that runs on it.


High-Level Architecture

flowchart TB subgraph Platform ["Platform Abstraction Layer (PAL)"] Window[Window/Input] Audio[Audio Backend] GPU[Graphics API
Vulkan/DX12/Metal] FS[File System] Net[Network] Thread[Threading] end subgraph Core ["Core Systems"] Mem[Memory/Allocators] Job[Job System] Asset[Asset Pipeline] Config[Configuration] Log[Logging/Profiler] end subgraph Runtime ["Runtime Systems"] ECS[ECS / Scene Graph] Physics[Physics] Render[Renderer] AudioSys[Audio Engine] Script[Scripting VM] NetSys[Networking] UI[UI System] end subgraph Tools ["Editor/Tooling"] Editor[Level Editor] AssetTools[Asset Pipeline] Debug[Debug Visualizers] Perf[Profiler UI] Build[Build Pipeline] end %% Connections - using direction to flow down naturally Platform --> Core Core --> Runtime Runtime --> Tools Tools -.-> Asset %% Styling for subgraphs classDef pal fill:#e3f2fd,stroke:#1976d2,stroke-width:2px; classDef core fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px; classDef runtime fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; classDef tools fill:#fff3e0,stroke:#f57c00,stroke-width:2px; class Window,Audio,GPU,FS,Net,Thread pal; class Mem,Job,Asset,Config,Log core; class ECS,Physics,Render,AudioSys,Script,NetSys,UI runtime; class Editor,AssetTools,Debug,Perf,Build tools;

1. Platform Abstraction Layer (PAL)

Why Abstract?

Platform Windowing Graphics Audio Threading
Windows Win32 DX12, Vulkan XAudio2, WASAPI Win32 threads
Linux Wayland/X11 Vulkan PipeWire/PulseAudio pthreads
macOS Cocoa Metal CoreAudio pthreads/GCD
iOS UIKit Metal AVAudio pthreads/GCD
Android NativeActivity Vulkan OpenSL/AAudio pthreads
Consoles Proprietary Proprietary Proprietary Proprietary

PAL Interface Example

# pal/window.py
from dataclasses import dataclass
from abc import ABC, abstractmethod
from typing import Optional

@dataclass
class WindowDesc:
    width: int = 1280
    height: int = 720
    title: str = "Game Window"
    fullscreen: bool = False
    vsync: bool = True

class IWindow(ABC):
    @abstractmethod
    def create(self, desc: WindowDesc) -> bool: ...
    @abstractmethod
    def destroy(self) -> None: ...
    @abstractmethod
    def poll_events(self) -> None: ...
    @abstractmethod
    def swap_buffers(self) -> None: ...
    @abstractmethod
    def get_native_handle(self) -> int: ...
    @abstractmethod
    def get_size(self) -> tuple[int, int]: ...
    @abstractmethod
    def set_title(self, title: str) -> None: ...

def create_window(desc: WindowDesc) -> Optional[IWindow]:
    """Factory - implementation per platform"""
    pass

# Implementation per platform:
# pal/win32/window.py, pal/linux/window.py, etc.
// pal/window.h
#include <string>
#include <memory>
#include <cstdint>

struct WindowDesc {
    uint32_t width = 1280;
    uint32_t height = 720;
    std::string title = "Game Window";
    bool fullscreen = false;
    bool vsync = true;
};

class IWindow {
public:
    virtual ~IWindow() = default;
    virtual bool create(const WindowDesc& desc) = 0;
    virtual void destroy() = 0;
    virtual void pollEvents() = 0;
    virtual void swapBuffers() = 0;
    virtual void* getNativeHandle() = 0;  // For graphics API
    virtual int getWidth() const = 0;
    virtual int getHeight() const = 0;
    virtual void setTitle(const std::string&) = 0;
};

std::unique_ptr<IWindow> createWindow(const WindowDesc& desc);

// Implementation per platform:
// pal/win32/window.cpp, pal/linux/window.cpp, etc.
// pal/Window.java
package engine.pal;

public interface IWindow {
    void create(WindowDesc desc);
    void destroy();
    void pollEvents();
    void swapBuffers();
    long getNativeHandle();  // For graphics API
    int getWidth();
    int getHeight();
    void setTitle(String title);
}

public record WindowDesc(
    int width,
    int height,
    String title,
    boolean fullscreen,
    boolean vsync
) {
    public WindowDesc() { this(1280, 720, "Game Window", false, true); }
}

public final class WindowFactory {
    public static IWindow createWindow(WindowDesc desc) {
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains("win")) return new Win32Window();
        if (os.contains("linux")) return new LinuxWindow();
        if (os.contains("mac")) return new CocoaWindow();
        throw new UnsupportedOperationException("Platform not supported");
    }
}
using System;

namespace Engine.PAL {

public interface IWindow {
    void Create(WindowDesc desc);
    void Destroy();
    void PollEvents();
    void SwapBuffers();
    IntPtr GetNativeHandle();  // For graphics API
    int Width { get; }
    int Height { get; }
    void SetTitle(string title);
}

public record WindowDesc(
    uint Width = 1280,
    uint Height = 720,
    string Title = "Game Window",
    bool Fullscreen = false,
    bool VSync = true
);

public static class WindowFactory {
    public static IWindow CreateWindow(WindowDesc desc) {
        var os = Environment.OSVersion.Platform;
        if (os == PlatformID.Win32NT) return new Win32Window();
        if (os == PlatformID.Unix) return new LinuxWindow();
        if (os == PlatformID.MacOSX) return new CocoaWindow();
        throw new NotSupportedException("Platform not supported");
    }
}

2. Memory Management

Allocator Strategy

# Linear allocator โ€” frame/temporary allocations
class LinearAllocator:
    def __init__(self, size: int):
        self.size = size
        self.offset = 0
        self.buffer = bytearray(size)

    def allocate(self, size: int, alignment: int = 16) -> memoryview:
        self.offset = (self.offset + alignment - 1) & ~(alignment - 1)
        assert self.offset + size <= self.size, "Out of memory"
        ptr = memoryview(self.buffer)[self.offset:self.offset + size]
        self.offset += size
        return ptr

    def reset(self) -> None:
        self.offset = 0  # Frame boundary

# Pool allocator โ€” fixed-size objects (entities, components)
from typing import TypeVar, Generic, List
T = TypeVar('T')

class PoolAllocator(Generic[T]):
    def __init__(self, factory, initial_size: int = 100):
        self.factory = factory
        self.pool: List[T] = [factory() for _ in range(initial_size)]
        self.free_list: List[int] = list(range(initial_size))

    def allocate(self) -> T:
        if self.free_list:
            idx = self.free_list.pop()
            return self.pool[idx]
        self.pool.append(self.factory())
        return self.pool[-1]

    def deallocate(self, obj: T) -> None:
        idx = self.pool.index(obj)
        self.free_list.append(idx)

# Stack allocator โ€” scoped allocations with automatic cleanup
class StackAllocator:
    def __init__(self, size: int):
        self.base = LinearAllocator(size)

    class Marker:
        def __init__(self, offset: int):
            self.offset = offset

    def get_marker(self) -> Marker:
        return self.base.Marker(self.base.offset)

    def free_to_marker(self, marker: Marker) -> None:
        self.base.offset = marker.offset

# Standard allocator with tracking (debug)
class TrackingAllocator:
    def __init__(self):
        self.allocations = {}
        import threading
        self.mutex = threading.Lock()

    def allocate(self, size: int, file: str, line: int) -> memoryview:
        with self.mutex:
            ptr = bytearray(size)
            self.allocations[id(ptr)] = (size, file, line)
            return memoryview(ptr)

    def deallocate(self, ptr: memoryview) -> None:
        with self.mutex:
            if id(ptr) in self.allocations:
                del self.allocations[id(ptr)]

    def report_leaks(self) -> None:
        for ptr, (size, file, line) in self.allocations.items():
            print(f"LEAK: {size} bytes at {file}:{line}")
// Linear allocator โ€” frame/temporary allocations
class LinearAllocator {
    char* buffer;
    size_t offset, size;
public:
    LinearAllocator(size_t size) : size(size), offset(0) {
        buffer = static_cast<char*>(malloc(size));
    }
    void* allocate(size_t size, size_t alignment = 16) {
        offset = (offset + alignment - 1) & ~(alignment - 1);
        assert(offset + size <= this->size);
        void* ptr = buffer + offset;
        offset += size;
        return ptr;
    }
    void reset() { offset = 0; }  // Frame boundary
    ~LinearAllocator() { free(buffer); }
};

// Pool allocator โ€” fixed-size objects (entities, components)
template<typename T>
class PoolAllocator {
    std::vector<T> pool;
    std::vector<size_t> freeList;
public:
    explicit PoolAllocator(size_t initial = 100) {
        pool.reserve(initial);
        for (size_t i = 0; i < initial; ++i) {
            pool.emplace_back();
            freeList.push_back(i);
        }
    }
    template<typename... Args>
    T* allocate(Args&&... args) {
        T* obj;
        if (!freeList.empty()) {
            obj = &pool[freeList.back()];
            freeList.pop_back();
            new (obj) T();  // Placement new
        } else {
            pool.emplace_back();
            obj = &pool.back();
        }
        return obj;
    }
    void deallocate(T* ptr) {
        ptr->~T();
        freeList.push_back(ptr - pool.data());
    }
};

// Stack allocator โ€” scoped allocations with automatic cleanup
class StackAllocator {
    struct Marker { size_t offset; };
    LinearAllocator base;
public:
    Marker getMarker() { return {base.offset}; }
    void freeToMarker(Marker m) { base.offset = m.offset; }
};

// Standard allocator with tracking (debug)
class TrackingAllocator {
    std::unordered_map<void*, AllocationInfo> allocations;
    std::mutex mutex;
public:
    void* allocate(size_t size, const char* file, int line) {
        std::lock_guard lock(mutex);
        void* ptr = malloc(size);
        allocations[ptr] = {size, file, line};
        return ptr;
    }
    void deallocate(void* ptr) {
        std::lock_guard lock(mutex);
        allocations.erase(ptr);
        free(ptr);
    }
    void reportLeaks() const {
        for (auto& [ptr, info] : allocations)
            std::cout << "LEAK: " << info.size << "B at " << info.file << ":" << info.line << "\n";
    }
};
import java.util.*;
import java.util.concurrent.locks.*;

public final class LinearAllocator {
    private final byte[] buffer;
    private int offset, size;

    public LinearAllocator(int size) {
        this.size = size;
        this.buffer = new byte[size];
    }

    public ByteBuffer allocate(int size, int alignment) {
        offset = (offset + alignment - 1) & ~(alignment - 1);
        if (offset + size > this.size) throw new OutOfMemoryError();
        int pos = offset;
        offset += size;
        return ByteBuffer.wrap(buffer, pos, size).asReadOnlyBuffer();
    }

    public void reset() { offset = 0; }
}

public final class PoolAllocator<T> {
    private final Supplier<T> factory;
    private final List<T> pool = new ArrayList<>();
    private final Deque<Integer> freeList = new ArrayDeque<>();

    public PoolAllocator(Supplier<T> factory, int initial) {
        this.factory = factory;
        for (int i = 0; i < initial; i++) {
            pool.add(factory.get());
            freeList.add(i);
        }
    }

    public T allocate() {
        if (!freeList.isEmpty()) return pool.get(freeList.pop());
        T obj = factory.get();
        pool.add(obj);
        return obj;
    }

    public void deallocate(T obj) {
        int idx = pool.indexOf(obj);
        if (idx >= 0) freeList.add(idx);
    }
}

public final class StackAllocator {
    private final LinearAllocator base = new LinearAllocator(0);

    public record Marker(int offset) {}

    public Marker getMarker() { return new Marker(offset); }
    public void freeToMarker(Marker m) { offset = m.offset; }
}

public final class TrackingAllocator {
    private final Map<Long, AllocationInfo> allocations = new ConcurrentHashMap<>();

    public ByteBuffer allocate(int size, String file, int line) {
        ByteBuffer buf = ByteBuffer.allocateDirect(size);
        allocations.put((long) buf.hashCode(), new AllocationInfo(size, file, line));
        return buf;
    }

    public void deallocate(ByteBuffer buf) {
        allocations.remove((long) buf.hashCode());
    }

    public void reportLeaks() {
        allocations.forEach((k, v) -> 
            System.out.println("LEAK: " + v.size + "B at " + v.file + ":" + v.line));
    }

    private record AllocationInfo(int size, String file, int line) {}
}
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

// Linear allocator โ€” frame/temporary allocations
public unsafe class LinearAllocator {
    private byte* buffer;
    private int offset, size;

    public LinearAllocator(int size) {
        this.size = size;
        this.buffer = (byte*) Marshal.AllocHGlobal(size);
    }

    public Span<byte> Allocate(int size, int alignment = 16) {
        offset = (offset + alignment - 1) & ~(alignment - 1);
        if (offset + size > this.size) throw new OutOfMemoryException();
        int pos = offset;
        offset += size;
        return new Span<byte>(buffer + pos, size);
    }

    public void Reset() => offset = 0;
    public void Dispose() {
        if (buffer != null) Marshal.FreeHGlobal((IntPtr)buffer);
        buffer = null;
    }
}

// Pool allocator โ€” fixed-size objects (entities, components)
public class PoolAllocator<T> where T : new() {
    private readonly List<T> pool = new();
    private readonly Stack<int> freeList = new();

    public PoolAllocator(int initial = 100) {
        for (int i = 0; i < initial; i++) {
            pool.Add(new T());
            freeList.Push(i);
        }
    }

    public T Allocate() {
        if (freeList.Count > 0) return pool[freeList.Pop()];
        var obj = new T();
        pool.Add(obj);
        pool.Add(obj);
        return obj;
    }

    public void Deallocate(T obj) {
        int idx = pool.IndexOf(obj);
        if (idx >= 0) freeList.Push(idx);
    }
}

// Stack allocator โ€” scoped allocations with automatic cleanup
public class StackAllocator {
    private readonly LinearAllocator baseAlloc = new();

    public readonly struct Marker { public readonly int Offset; }

    public Marker GetMarker() => new() { Offset = baseAlloc.Offset };
    public void FreeToMarker(Marker m) => baseAlloc.Offset = m.Offset;
}

// Standard allocator with tracking (debug)
public class TrackingAllocator {
    private readonly Dictionary<IntPtr, AllocationInfo> allocations = new();
    private readonly Mutex mutex = new();

    public IntPtr Allocate(int size, string file, int line) {
        lock (mutex) {
            IntPtr ptr = Marshal.AllocHGlobal(size);
            allocations[ptr] = new AllocationInfo(size, file, line);
            return ptr;
        }
    }

    public void Deallocate(IntPtr ptr) {
        lock (mutex) {
            allocations.Remove(ptr);
            Marshal.FreeHGlobal(ptr);
        }
    }

    public void ReportLeaks() {
        foreach (var (ptr, info) in allocations)
            Console.WriteLine($"LEAK: {info.Size}B at {info.File}:{info.Line}");
    }

    public readonly record struct AllocationInfo(int Size, string File, int Line);
}

Allocation Rules

Allocator Use For Lifetime
System heap Engine subsystems, long-lived Process
Frame linear Per-frame render data, temp math Frame
Pool Entities, components, particles Object lifetime
Stack Scoped function allocations Scope
Tracking Debug builds only N/A

3. Job System (Parallelism)

Why Job System?

Games need frame-parallelism (render + sim + audio) and data-parallelism (culling, skinning, particles).

import threading
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import Callable, Optional
import concurrent.futures

@dataclass
class JobHandle:
    counter: int = 1

class JobSystem:
    def __init__(self, num_threads: int = None):
        self.workers = ThreadPoolExecutor(max_workers=num_threads or threading.cpu_count())
        self._queue = concurrent.futures.Queue()
        self._running = True

    # Fire-and-forget
    def dispatch(self, func: Callable[[], None]) -> JobHandle:
        handle = JobHandle()
        self._queue.put((func, handle))
        return handle

    # With dependency
    def dispatch_with_deps(self, func: Callable[[], None], deps: list[JobHandle]) -> JobHandle:
        h = JobHandle(1 + len(deps))
        for d in deps:
            d.counter -= 1
        self._queue.put((func, h))
        return h

    def wait(self, handle: JobHandle) -> None:
        while handle.counter > 0:
            self._execute_one()

    def wait_all(self) -> None:
        pass  # drain queue

    def _execute_one(self) -> None:
        try:
            func, handle = self._queue.get_nowait()
            if handle.counter == 0:
                func()
            else:
                self._queue.put((func, handle))
        except Exception:
            pass
#include <functional>
#include <vector>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>

class JobSystem {
    struct Job {
        std::function<void()> work;
        std::atomic<int>* counter;
    };

    struct Handle { std::atomic<int> counter{1}; };

    std::vector<std::thread> workers;
    std::queue<Job> queue;
    std::mutex queue_mutex;
    std::atomic<bool> running{true};

    JobSystem(int numThreads = std::thread::hardware_concurrency());

    // Fire-and-forget
    template<typename F>
    void dispatch(F&& f) {
        std::lock_guard lock(queue_mutex);
        queue.push({std::forward<F>(f), nullptr});
    }

    // With dependency
    template<typename F>
    Handle dispatch(F&& f, Handle* deps, int numDeps) {
        Handle h;
        h.counter.store(1 + numDeps);
        for (int i = 0; i < numDeps; ++i) deps[i].counter.fetch_sub(1);
        std::lock_guard lock(queue_mutex);
        queue.push({std::forward<F>(f), &h.counter});
        return h;
    }

    void wait(const Handle& h) {
        while (h.counter.load() > 0) executeOne();
    }

    void waitAll() { /* drain queue */ }

private:
    void executeOne();
};
import java.util.concurrent.*;
import java.util.function.*;
import java.util.*;

public final class JobSystem {
    private record Job(Runnable work, AtomicInteger counter) {}

    public static class Handle {
        public final AtomicInteger counter = new AtomicInteger(1);
    }

    private final ExecutorService executor;
    private final ConcurrentLinkedQueue<Job> queue = new ConcurrentLinkedQueue<>();
    private final AtomicBoolean running = new AtomicBoolean(true);

    public JobSystem(int threads) {
        executor = Executors.newFixedThreadPool(threads);
    }

    public void dispatch(Runnable work) {
        queue.add(new Job(work, null));
    }

    public Handle dispatch(Runnable work, Handle... deps) {
        Handle h = new Handle();
        h.counter.set(1 + deps.length);
        for (Handle d : deps) d.counter.decrementAndGet();
        queue.add(new Job(work, h.counter));
        return h;
    }

    public void wait(Handle h) {
        while (h.counter.get() > 0) executeOne();
    }

    public void waitAll() { /* drain queue */ }

    private void executeOne() {
        Job job = queue.poll();
        if (job != null && (job.counter == null || job.counter.get() == 0))
            job.work.run();
        else if (job != null) queue.add(job);
    }
}
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class JobSystem {
    public class Handle {
        public int Counter = 1;
    }

    private readonly ConcurrentQueue<(Action Work, Handle Counter)> _queue = new();
    private readonly Thread[] _workers;
    private readonly CancellationTokenSource _cts = new();

    public JobSystem(int threads = -1) {
        threads = threads > 0 ? threads : Environment.ProcessorCount;
        _workers = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            _workers[i] = new Thread(ExecuteLoop) { IsBackground = true };
            _workers[i].Start();
        }
    }

    public void Dispatch(Action work) {
        _queue.Enqueue((work, null));
    }

    public Handle Dispatch(Action work, params Handle[] deps) {
        var h = new Handle { Counter = 1 + deps.Length };
        foreach (var d in deps) Interlocked.Decrement(ref d.Counter);
        _queue.Enqueue((work, h));
        return h;
    }

    public void Wait(Handle h) {
        while (Volatile.Read(ref h.Counter) > 0) ExecuteOne();
    }

    public void WaitAll() { }

    private void ExecuteLoop() {
        while (!_cts.IsCancellationRequested) {
            ExecuteOne();
        }
    }

    private void ExecuteOne() {
        if (_queue.TryDequeue(out var job)) {
            if (job.Counter == null || job.Counter.Counter == 0)
                job.Work();
            else
                _queue.Enqueue(job);
        }
    }
}

Frame Pipeline with Jobs

def tick(dt: float) -> None:
    # 1. Input (main thread)
    input.poll()

    # 2. Parallel: Simulation + Render prep
    sim_handle = jobs.dispatch(lambda: (physics.step(dt), ecs.update(dt)))
    cull_handle = jobs.dispatch(lambda: (frustum_cull(camera), occlusion_cull()))

    # 3. Wait for sim done (render needs positions)
    jobs.wait(sim_handle)

    # 4. Render command generation (main or render thread)
    render.generate_commands()

    # 5. Audio (separate thread)
    audio.update()

    # 6. Present
    window.swap_buffers()

    # 6.5 Frame cleanup
    frame_allocator.reset()
void Game::tick(float dt) {
    // 1. Input (main thread)
    input.poll();

    // 2. Parallel: Simulation + Render prep
    JobHandle simHandle = jobs.dispatch([&] {
        physics.step(dt);
        ecs.update(dt);  // Systems in parallel via archetypes
    });

    JobHandle cullHandle = jobs.dispatch([&] {
        frustumCull(camera);
        occlusionCull();
    });

    // 3. Wait for sim done (render needs positions)
    jobs.wait(simHandle);

    // 4. Render command generation (main or render thread)
    render.generateCommands();

    // 5. Audio (separate thread)
    audio.update();

    // 6. Present
    window.swapBuffers();

    // 6.5 Frame cleanup
    frameAllocator.reset();
}
public void tick(float dt) {
    // 1. Input (main thread)
    input.poll();

    // 2. Parallel: Simulation + Render prep
    JobSystem.Handle simHandle = jobs.dispatch(() -> {
        physics.step(dt);
        ecs.update(dt);
    });

    JobSystem.Handle cullHandle = jobs.dispatch(() -> {
        frustumCull(camera);
        occlusionCull();
    });

    // 3. Wait for sim done (render needs positions)
    jobs.wait(simHandle);

    // 4. Render command generation (main or render thread)
    render.generateCommands();

    // 5. Audio (separate thread)
    audio.update();

    // 6. Present
    window.swapBuffers();

    // 6.5 Frame cleanup
    frameAllocator.reset();
}
public void Tick(float dt) {
    // 1. Input (main thread)
    _input.Poll();

    // 2. Parallel: Simulation + Render prep
    var simHandle = _jobs.Dispatch(() => {
        _physics.Step(dt);
        _ecs.Update(dt);
    });

    var cullHandle = _jobs.Dispatch(() => {
        FrustumCull(_camera);
        OcclusionCull();
    });

    // 3. Wait for sim done (render needs positions)
    _jobs.Wait(simHandle);

    // 4. Render command generation (main or render thread)
    _render.GenerateCommands();

    // 5. Audio (separate thread)
    _audio.Update();

    // 6. Present
    _window.SwapBuffers();

    // 6.5 Frame cleanup
    _frameAllocator.Reset();
}

4. Asset Pipeline

Build-Time vs Runtime

Phase Operations
Import Source (FBX, PSD, WAV) โ†’ Engine intermediate
Process Optimize, compress, generate mipmaps, bake
Package Bundle into platform-specific archives
Runtime Load Stream, decompress, GPU upload

Intermediate Formats

from dataclasses import dataclass
from typing import List, Dict

@dataclass
class Vertex:
    position: tuple[float, float, float]
    normal: tuple[float, float, float]
    uv: tuple[float, float]
    tangent: tuple[float, float, float, float]

@dataclass
class MeshAsset:
    vertices: List[Vertex]
    indices: List[int]  # 16 or 32-bit
    submeshes: List['Submesh']
    bounds: 'Bounds'
    skeleton: Optional['Skeleton'] = None
    blend_shapes: List['BlendShape'] = []

@dataclass
class TextureAsset:
    width: int
    height: int
    depth: int
    mip_levels: int
    format: str  # BC7, ASTC, etc.
    data: bytes  # Compressed
    is_srgb: bool
    is_normal_map: bool

@dataclass
class MaterialAsset:
    shader: str  # Shader permutation key
    textures: Dict[str, 'TextureHandle']
    scalars: Dict[str, float]
    vectors: Dict[str, tuple[float, float, float, float]]
    state: 'RenderState'
// Mesh intermediate (after import, before GPU)
struct Vertex {
    Vec3 position, normal, tangent;
    Vec2 uv;
};

struct MeshAsset {
    std::vector<Vertex> vertices;     // Position, normal, UV, tangent
    std::vector<uint32_t> indices;    // 16 or 32-bit
    std::vector<Submesh> submeshes;   // Material ranges
    Bounds bounds;                    // AABB, sphere
    Skeleton skeleton;                // Optional
    std::vector<BlendShape> shapes;   // Optional
};

// Texture intermediate
struct TextureAsset {
    uint32_t width, height, depth;
    uint32_t mipLevels;
    Format format;                    // BC7, ASTC, etc.
    std::vector<uint8_t> data;        // Compressed
    bool isSRGB;
    bool isNormalMap;
};

// Material intermediate
struct MaterialAsset {
    std::string shader;               // Shader permutation key
    std::unordered_map<std::string, TextureHandle> textures;
    std::unordered_map<std::string, float> scalars;
    std::unordered_map<std::string, Vec4> vectors;
    RenderState state;                // Blend, depth, cull
};
import java.util.*;
import java.util.function.*;

public final class Vertex {
    public final Vec3 position, normal, tangent;
    public final Vec2 uv;
    public Vertex(Vec3 pos, Vec3 norm, Vec3 tan, Vec2 uv) {
        this.position = pos; this.normal = norm; this.tangent = tan; this.uv = uv;
    }
}

public final class MeshAsset {
    public final List<Vertex> vertices;     // Position, normal, UV, tangent
    public final List<Integer> indices;     // 16 or 32-bit
    public final List<Submesh> submeshes;   // Material ranges
    public final Bounds bounds;             // AABB, sphere
    public final Skeleton skeleton;         // Optional
    public final List<BlendShape> shapes;   // Optional
}

public final class TextureAsset {
    public final int width, height, depth;
    public final int mipLevels;
    public final Format format;              // BC7, ASTC, etc.
    public final byte[] data;                // Compressed
    public final boolean isSRGB;
    public final boolean isNormalMap;
}

public final class MaterialAsset {
    public final String shader;              // Shader permutation key
    public final Map<String, TextureHandle> textures;
    public final Map<String, Float> scalars;
    public final Map<String, Vec4> vectors;
    public final RenderState state;          // Blend, depth, cull
}
// Mesh intermediate (after import, before GPU)
public readonly record struct Vertex(
    Vec3 Position, Vec3 Normal, Vec3 Tangent, Vec2 UV
);

public sealed class MeshAsset {
    public List<Vertex> Vertices { get; } = new();
    public List<int> Indices { get; } = new();      // 16 or 32-bit
    public List<Submesh> Submeshes { get; } = new();
    public Bounds Bounds { get; init; }
    public Skeleton Skeleton { get; init; }
    public List<BlendShape> Shapes { get; } = new();
}

public sealed class TextureAsset {
    public uint Width, Height, Depth;
    public uint MipLevels;
    public Format Format;              // BC7, ASTC, etc.
    public byte[] Data;                // Compressed
    public bool IsSRGB;
    public bool IsNormalMap;
}

public sealed class MaterialAsset {
    public string Shader;                              // Shader permutation key
    public Dictionary<string, TextureHandle> Textures { get; } = new();
    public Dictionary<string, float> Scalars { get; } = new();
    public Dictionary<string, Vec4> Vectors { get; } = new();
    public RenderState State { get; init; }
}

Asset Registry (Runtime)

from weakref import WeakValueDictionary
from typing import TypeVar, Generic

T = TypeVar('T')

class AssetRegistry:
    def __init__(self):
        self._assets = {}
        self._path_to_id = {}
        self._gpu_cache = LRUCache()

    def load(self, asset_id: T) -> T:
        entry = self._assets[asset_id]
        if ptr := entry.data():
            entry.last_used_frame = frame
            return ptr
        # Load from disk โ†’ process โ†’ upload GPU
        data = load_from_package(asset_id)
        entry.data = data
        entry.last_used_frame = frame
        return data

    def update(self, budget_bytes: int) -> None:
        # Evict LRU entries over budget
        while self.current_size > budget_bytes:
            evict_oldest()
class AssetRegistry {
    struct AssetEntry {
        AssetID id;
        std::string path;
        std::weak_ptr<void> data;     // Auto-unload if unused
        uint64_t lastUsedFrame;
        size_t sizeBytes;
    };

    std::unordered_map<AssetID, AssetEntry> assets;
    std::unordered_map<std::string, AssetID> pathToID;
    LRUCache<AssetID, std::shared_ptr<void>> gpuCache;

public:
    template<typename T>
    std::shared_ptr<T> load(AssetID id) {
        auto& entry = assets[id];
        if (auto ptr = entry.data.lock()) {
            entry.lastUsedFrame = frame;
            return std::static_pointer_cast<T>(ptr);
        }
        // Load from disk โ†’ process โ†’ upload GPU
        auto data = loadFromPackage<T>(id);
        entry.data = data;
        entry.lastUsedFrame = frame;
        return data;
    }

    void update(size_t budgetBytes) {
        // Evict LRU entries over budget
        while (currentSize > budgetBytes) evictOldest();
    }
};
public final class AssetRegistry {
    private final Map<AssetID, AssetEntry> assets = new ConcurrentHashMap<>();
    private final Map<String, AssetID> pathToID = new ConcurrentHashMap<>();
    private final LRUCache<AssetID, Object> gpuCache = new LRUCache<>();

    public record AssetEntry(
        AssetID id, String path,
        WeakReference<Object> data,
        long lastUsedFrame, long sizeBytes
    ) {}

    @SuppressWarnings("unchecked")
    public <T> T load(AssetID id) {
        AssetEntry entry = assets.get(id);
        if (entry != null && entry.data.get() != null) {
            entry.lastUsedFrame = frame;
            return (T) entry.data.get();
        }
        T data = loadFromPackage(id);
        entry.data = new WeakReference<>(data);
        entry.lastUsedFrame = frame;
        return data;
    }

    public void update(long budgetBytes) {
        while (currentSize > budgetBytes) evictOldest();
    }
}
public class AssetRegistry {
    private readonly Dictionary<AssetID, AssetEntry> _assets = new();
    private readonly Dictionary<string, AssetID> _pathToId = new();
    private readonly LRUCache<AssetID, object> _gpuCache = new();

    private record AssetEntry(
        AssetID Id, string Path,
        WeakReference Data,
        long LastUsedFrame, long SizeBytes
    );

    public T Load<T>(AssetID id) where T : class {
        if (_assets.TryGetValue(id, out var entry) && entry.Data.IsAlive) {
            entry.LastUsedFrame = _frame;
            return (T)entry.Data.Target!;
        }
        var data = LoadFromPackage<T>(id);
        _assets[id] = new AssetEntry(id, "", new WeakReference(data), _frame, 0);
        return data;
    }

    public void Update(long budgetBytes) {
        while (CurrentSize > budgetBytes) EvictOldest();
    }
}

5. Renderer Architecture

Render Graph (Modern)

from typing import Callable, List
from dataclasses import dataclass

@dataclass
class ResourceRef:
    name: str

@dataclass
class Pass:
    name: str
    execute: Callable[['RenderContext'], None]
    reads: List[ResourceRef] = field(default_factory=list)
    writes: List[ResourceRef] = field(default_factory=list)

class RenderGraph:
    def __init__(self):
        self.passes: List[Pass] = []
        self.resources = ResourcePool()  # Transient (RTs, buffers)

    def add_pass(self, name: str, execute: Callable[['RenderContext'], None]) -> 'RenderGraph':
        self.passes.append(Pass(name, execute))
        return self

    def reads(self, ref: ResourceRef) -> 'RenderGraph':
        self.passes[-1].reads.append(ref)
        return self

    def writes(self, ref: ResourceRef) -> 'RenderGraph':
        self.passes[-1].writes.append(ref)
        return self

    def compile(self) -> None:
        pass  # Topological sort, alias transient resources

    def execute(self, ctx: 'RenderContext') -> None:
        for pass_ in self.passes:
            pass_.execute(ctx)


# Usage per frame
rg = RenderGraph()
rg.add_pass("ShadowMap", lambda c: render_shadows(c)) \
  .writes(ResourceRef("shadowMap")) \
  .reads(ResourceRef("sceneDepth"))

rg.add_pass("GBuffer", lambda c: render_gbuffer(c)) \
  .writes(ResourceRef("gbuffer0"), ResourceRef("gbuffer1"), 
          ResourceRef("gbuffer2"), ResourceRef("depth")) \
  .reads(ResourceRef("shadowMap"))

rg.add_pass("Lighting", lambda c: render_deferred(c)) \
  .writes(ResourceRef("hdrColor")) \
  .reads(ResourceRef("gbuffer0"), ResourceRef("gbuffer1"),
          ResourceRef("gbuffer2"), ResourceRef("depth"), 
          ResourceRef("shadowMap"))

rg.add_pass("PostProcess", lambda c: post_process(c)) \
  .writes(ResourceRef("backbuffer")) \
  .reads(ResourceRef("hdrColor"))

rg.compile()
rg.execute(ctx)
// Frame = DAG of render passes with resource dependencies
class RenderGraph {
    struct Pass {
        std::string name;
        std::function<void(RenderContext&)> execute;
        std::vector<ResourceRef> reads, writes;
    };

    std::vector<Pass> passes;
    ResourcePool resources;  // Transient (RTs, buffers)

public:
    RenderGraph& addPass(std::string name, auto&& execute) {
        passes.push_back({name, execute, {}, {}});
        return *this;
    }

    RenderGraph& reads(ResourceRef r) { passes.back().reads.push_back(r); return *this; }
    RenderGraph& writes(ResourceRef r) { passes.back().writes.push_back(r); return *this; }

    void compile() { /* Topological sort, alias transient resources */ }
    void execute(RenderContext& ctx) { for (auto& p : passes) p.execute(ctx); }
};

// Usage per frame
RenderGraph rg;
rg.addPass("ShadowMap", [&](Ctx& c) { renderShadows(c); })
  .writes("shadowMap")
  .reads("sceneDepth");

rg.addPass("GBuffer", [&](Ctx& c) { renderGBuffer(c); })
  .writes("gbuffer0", "gbuffer1", "gbuffer2", "depth")
  .reads("shadowMap");

rg.addPass("Lighting", [&](Ctx& c) { renderDeferred(c); })
  .writes("hdrColor")
  .reads("gbuffer0", "gbuffer1", "gbuffer2", "depth", "shadowMap");

rg.addPass("PostProcess", [&](Ctx& c) { postProcess(c); })
  .writes("backbuffer")
  .reads("hdrColor");

rg.compile();
rg.execute(ctx);
import java.util.*;
import java.util.function.*;

public final class RenderGraph {
    public record Pass(String name, Consumer<RenderContext> execute,
                       List<ResourceRef> reads, List<ResourceRef> writes) {}

    private final List<Pass> passes = new ArrayList<>();
    private final ResourcePool resources = new ResourcePool();

    public RenderGraph addPass(String name, Consumer<RenderContext> execute) {
        passes.add(new Pass(name, execute, new ArrayList<>(), new ArrayList<>()));
        return this;
    }

    public RenderGraph reads(ResourceRef r) { passes.getLast().reads.add(r); return this; }
    public RenderGraph writes(ResourceRef r) { passes.getLast().writes.add(r); return this; }

    public void compile() { /* Topological sort, alias transient resources */ }
    public void execute(RenderContext ctx) { for (Pass p : passes) p.execute().accept(ctx); }
}

// Usage per frame
RenderGraph rg = new RenderGraph();
rg.addPass("ShadowMap", c -> renderShadows(c))
  .writes(new ResourceRef("shadowMap"))
  .reads(new ResourceRef("sceneDepth"));

rg.addPass("GBuffer", c -> renderGBuffer(c))
  .writes(new ResourceRef("gbuffer0"), new ResourceRef("gbuffer1"),
          new ResourceRef("gbuffer2"), new ResourceRef("depth"))
  .reads(new ResourceRef("shadowMap"));

rg.addPass("Lighting", c -> renderDeferred(c))
  .writes(new ResourceRef("hdrColor"))
  .reads(new ResourceRef("gbuffer0"), new ResourceRef("gbuffer1"),
          new ResourceRef("gbuffer2"), new ResourceRef("depth"), 
          new ResourceRef("shadowMap"));

rg.addPass("PostProcess", c -> postProcess(c))
  .writes(new ResourceRef("backbuffer"))
  .reads(new ResourceRef("hdrColor"));

rg.compile();
rg.execute(ctx);
using System;
using System.Collections.Generic;

public sealed class RenderGraph {
    public sealed record Pass(string Name, Action<RenderContext> Execute,
                              List<ResourceRef> Reads, List<ResourceRef> Writes);

    private readonly List<Pass> _passes = new();
    private readonly ResourcePool _resources = new();

    public RenderGraph AddPass(string name, Action<RenderContext> execute) {
        _passes.Add(new Pass(name, execute, new(), new()));
        return this;
    }

    public RenderGraph Reads(ResourceRef r) { _passes[^1].Reads.Add(r); return this; }
    public RenderGraph Writes(ResourceRef r) { _passes[^1].Writes.Add(r); return this; }

    public void Compile() { /* Topological sort, alias transient resources */ }
    public void Execute(RenderContext ctx) { foreach (var p in _passes) p.Execute(ctx); }
}

// Usage per frame
var rg = new RenderGraph();
rg.AddPass("ShadowMap", c => RenderShadows(c))
  .Writes(new ResourceRef("shadowMap"))
  .Reads(new ResourceRef("sceneDepth"));

rg.AddPass("GBuffer", c => RenderGBuffer(c))
  .Writes(new ResourceRef("gbuffer0"), new ResourceRef("gbuffer1"),
          new ResourceRef("gbuffer2"), new ResourceRef("depth"))
  .Reads(new ResourceRef("shadowMap"));

rg.AddPass("Lighting", c => RenderDeferred(c))
  .Writes(new ResourceRef("hdrColor"))
  .Reads(new ResourceRef("gbuffer0"), new ResourceRef("gbuffer1"),
          new ResourceRef("gbuffer2"), new ResourceRef("depth"), 
          new ResourceRef("shadowMap"));

rg.AddPass("PostProcess", c => PostProcess(c))
  .Writes(new ResourceRef("backbuffer"))
  .Reads(new ResourceRef("hdrColor"));

rg.Compile();
rg.Execute(ctx);

Shader System

@dataclass
class ShaderPermutation:
    base_name: str  # "pbr"
    defines: List[str]  # "SKINNING", "NORMAL_MAP", "IBL"

    def hash(self) -> int:
        return hash((self.base_name, tuple(self.defines)))


class ShaderLibrary:
    def __init__(self):
        self._cache = {}

    def get_or_compile(self, perm: ShaderPermutation) -> 'ShaderProgram':
        h = perm.hash()
        if h in self._cache:
            return self._cache[h]
        return self._compile_permutation(perm)  # Async in background

    def _compile_permutation(self, perm: ShaderPermutation) -> 'ShaderProgram':
        pass
// Permutation system for shader variants
struct ShaderPermutation {
    std::string baseName;           // "pbr"
    std::vector<std::string> defines;  // "SKINNING", "NORMAL_MAP", "IBL"
    uint64_t hash() const;          // Cache key
};

class ShaderLibrary {
    std::unordered_map<uint64_t, ShaderProgram> cache;

    ShaderProgram* getOrCompile(const ShaderPermutation& perm) {
        auto it = cache.find(perm.hash());
        if (it != cache.end()) return &it->second;
        return compilePermutation(perm);  // Async in background
    }
};
public final class ShaderPermutation {
    public final String baseName;           // "pbr"
    public final List<String> defines;  // "SKINNING", "NORMAL_MAP", "IBL"

    public long hash() { return Objects.hash(baseName, defines); }
}

public final class ShaderLibrary {
    private final Map<Long, ShaderProgram> cache = new ConcurrentHashMap<>();

    public ShaderProgram getOrCompile(ShaderPermutation perm) {
        return cache.computeIfAbsent(perm.hash(), k -> compilePermutation(perm));
    }
}
using System.Collections.Generic;

public readonly record struct ShaderPermutation {
    public readonly string BaseName;           // "pbr"
    public readonly List<string> Defines;  // "SKINNING", "NORMAL_MAP", "IBL"

    public ulong Hash() => HashCode.Combine(BaseName, HashCode.Combine(Defines));
}

public sealed class ShaderLibrary {
    private readonly Dictionary<ulong, ShaderProgram> _cache = new();

    public ShaderProgram GetOrCompile(ShaderPermutation perm) {
        return _cache.GetOrAdd(perm.Hash(), _ => CompilePermutation(perm));
    }
}

6. Physics Integration

Engine-Physics Boundary

class PhysicsSystem:
    def __init__(self):
        self.world = PhysicsWorld()  # PhysX, Jolt, Box2D
        self.accumulator = 0.0
        self.fixed_dt = 1.0 / 60.0

    def step(self, dt: float) -> None:
        self.accumulator += dt
        while self.accumulator >= self.fixed_dt:
            self.world.step(self.fixed_dt)
            self.accumulator -= self.fixed_dt
        self.interp_alpha = self.accumulator / self.fixed_dt

    def sync_to_engine(self, ecs: 'ECS') -> None:
        for entity, rigidbody, transform in ecs.view(RigidBody, Transform):
            pose = rigidbody.actor.get_global_pose()
            transform.pos = to_vec3(pose.p)
            transform.rot = to_quat(pose.q)

    def sync_from_engine(self, ecs: 'ECS') -> None:
        for entity, rigidbody, transform in ecs.view(RigidBody, Transform):
            if rigidbody.is_kinematic:
                rigidbody.actor.set_kinematic_target(to_px_transform(transform))
class PhysicsSystem {
    std::unique_ptr<PhysicsWorld> world;  // PhysX, Jolt, Box2D
    float accumulator = 0;
    const float fixedDt = 1.0f / 60.0f;

public:
    void step(float dt) {
        accumulator += dt;
        while (accumulator >= fixedDt) {
            world->step(fixedDt);
            accumulator -= fixedDt;
        }
        interpAlpha = accumulator / fixedDt;
    }

    void syncToEngine(ECS& ecs) {
        for (auto [entity, rigidbody, transform] : ecs.view<RigidBody, Transform>()) {
            auto pose = rigidbody.actor->getGlobalPose();
            transform.pos = toVec3(pose.p);
            transform.rot = toQuat(pose.q);
        }
    }

    void syncFromEngine(ECS& ecs) {
        for (auto [entity, rigidbody, transform] : ecs.view<RigidBody, Transform>()) {
            if (rigidbody.isKinematic) {
                rigidbody.actor->setKinematicTarget(toPxTransform(transform));
            }
        }
    }
};
public final class PhysicsSystem {
    private final PhysicsWorld world;
    private float accumulator = 0;
    private final float fixedDt = 1f / 60f;

    public void step(float dt) {
        accumulator += dt;
        while (accumulator >= fixedDt) {
            world.step(fixedDt);
            accumulator -= fixedDt;
        }
        interpAlpha = accumulator / fixedDt;
    }

    public void syncToEngine(ECS ecs) {
        for (var e : ecs.view(RigidBody.class, Transform.class)) {
            var pose = e.get(RigidBody.class).actor.getGlobalPose();
            e.get(Transform.class).pos = toVec3(pose.p);
            e.get(Transform.class).rot = toQuat(pose.q);
        }
    }

    public void syncFromEngine(ECS ecs) {
        for (var e : ecs.view(RigidBody.class, Transform.class)) {
            if (e.get(RigidBody.class).isKinematic) {
                e.get(RigidBody.class).actor.setKinematicTarget(toPxTransform(e.get(Transform.class)));
            }
        }
    }
}
public sealed class PhysicsSystem {
    private readonly PhysicsWorld _world;
    private float _accumulator = 0;
    private const float FixedDt = 1f / 60f;

    public void Step(float dt) {
        _accumulator += dt;
        while (_accumulator >= FixedDt) {
            _world.Step(FixedDt);
            _accumulator -= FixedDt;
        }
        InterpAlpha = _accumulator / FixedDt;
    }

    public void SyncToEngine(ECS ecs) {
        foreach (var (entity, rigidbody, transform) in ecs.View<ibrationBody, Transform>()) {
            var pose = rigidbody.Actor.GetGlobalPose();
            transform.Pos = ToVec3(pose.P);
            transform.Rot = ToQuat(pose.Q);
        }
    }

    public void SyncFromEngine(ECS ecs) {
        foreach (var (entity, rigidbody, transform) in ecs.View<RigidBody, Transform>()) {
            if (rigidbody.IsKinematic) {
                rigidbody.Actor.SetKinematicTarget(ToPxTransform(transform));
            }
        }
    }
}

Collision Callbacks

@dataclass
class CollisionEvent:
    a: Entity
    b: Entity
    point: Vec3
    normal: Vec3
    impulse: float

class PhysicsSystem:
    def __init__(self):
        self.collisions = EventBus[CollisionEvent]()

    def on_contact(self, pair: ContactPair) -> None:
        a = pair.shapes[0].get_user_data(Entity)
        b = pair.shapes[1].get_user_data(Entity)
        for contact in pair.contacts:
            self.collisions.publish(CollisionEvent(a, b, contact.point, contact.normal, contact.impulse))

# Gameplay subscribes
collisions.subscribe(lambda e: (
    e.a.get(Health).damage(10) if e.a.has(Player) and e.b.has(Enemy) else None,
    e.b.get(Audio).play("hit") if e.a.has(Player) and e.b.has(Enemy) else None
))
struct CollisionEvent {
    Entity a, b;
    Vec3 point, normal;
    float impulse;
};

class PhysicsSystem {
    EventBus<CollisionEvent> collisions;

    void onContact(const ContactPair& pair) {
        Entity a = pair.shapes[0].getUserData<Entity>();
        Entity b = pair.shapes[1].getUserData<Entity>();
        for (auto& contact : pair.contacts) {
            collisions.publish({a, b, contact.point, contact.normal, contact.impulse});
        }
    }
};

// Gameplay subscribes
collisions.subscribe([](const CollisionEvent& e) {
    if (e.a.has<Player>() && e.b.has<Enemy>()) {
        e.a.get<Health>().damage(10);
        e.b.get<Audio>().play("hit");
    }
});
public record CollisionEvent(Entity a, Entity b, Vec3 point, Vec3 normal, float impulse) {}

public final class PhysicsSystem {
    private final EventBus<CollisionEvent> collisions = new EventBus<>();

    public void onContact(ContactPair pair) {
        Entity a = pair.shapes[0].getUserData(Entity.class);
        Entity b = pair.shapes[1].getUserData(Entity.class);
        for (var contact : pair.contacts) {
            collisions.publish(new CollisionEvent(a, b, contact.point, contact.normal, contact.impulse));
        }
    }
}

// Gameplay subscribes
collisions.subscribe(e -> {
    if (e.a().has(Player.class) && e.b().has(Enemy.class)) {
        e.a().get(Health.class).damage(10);
        e.b().get(Audio.class).play("hit");
    }
});
public readonly record struct CollisionEvent(
    Entity A, Entity B, Vec3 Point, Vec3 Normal, float Impulse
);

public sealed class PhysicsSystem {
    private readonly EventBus<CollisionEvent> _collisions = new();

    public void OnContact(ContactPair pair) {
        Entity a = pair.Shapes[0].GetUserData<Entity>();
        Entity b = pair.Shapes[1].GetUserData<Entity>();
        foreach (var contact in pair.Contacts) {
            _collisions.Publish(new CollisionEvent(a, b, contact.Point, contact.Normal, contact.Impulse));
        }
    }
}

// Gameplay subscribes
_collisions.Subscribe(e => {
    if (e.A.Has<Player>() && e.B.Has<Enemy>()) {
        e.A.Get<Health>().Damage(10);
        e.B.Get<Audio>().Play("hit");
    }
});

7. SGA Globe Rendering โ€” Cross-Reference

Side Note: Our SGA Globe Rendering project (SGA Globe Rendering with Interval Arithmetic) demonstrates a specialized engine architecture for planetary-scale rendering:

Key Architectural Decisions from SGA Globe

Traditional Engine SGA Globe Approach
Ray-marching for terrain Interval arithmetic bisection โ€” mathematically rigorous root finding
CPU-side LOD GPU-driven LOD via interval subdivision
Double precision Spherical Geometric Algebra (SGA) โ€” coordinate-free math
Heightmap sampling Implicit SDF โ€” length(p) - (R + h(ฮธ,ฯ†))
Shadow maps Analytic visibility via interval bounds

Applicable Lessons for General Engines

  1. Implicit representations win for infinite/procedural content
  2. Interval arithmetic provides error bounds โ†’ automatic LOD
  3. Coordinate-free math (GA) eliminates singularities at poles
  4. GPU-driven pipelines shift work from CPU to compute shaders
  5. Mathematical rigor enables new rendering techniques (exact intersections)
# SGA Globe shader โ€” interval bisection (from paper)
class Interval:
    def __init__(self, lo: float, hi: float):
        self.lo, self.hi = lo, hi

    def mid(self) -> 'Interval':
        return Interval((self.lo + self.hi) * 0.5, (self.lo + self.hi) * 0.5)

def trace_globe(ro: Vec3, rd: Vec3) -> float:
    t = Interval(near, far)
    for _ in range(32):  # Sign change detection
        t_mid = t.mid()
        p = ro + rd * t_mid.lo
        d = sdf(p)  # Implicit SDF with interval arithmetic
        if d.lo <= 0.0 and d.hi >= 0.0:
            t = Interval(t.lo, t_mid.hi)  # Root in lower half
        else:
            t = Interval(t_mid.lo, t.hi)
    # Bisection refinement
    for _ in range(20):
        t_mid = (t.lo + t.hi) * 0.5
        d = sdf_scalar(ro + rd * t_mid)
        if d <= 0.0: t.hi = t_mid
        else: t.lo = t_mid
    return t.hi
// SGA Globe shader โ€” interval bisection (from paper)
struct Interval { float lo, hi; };
inline Interval mid(const Interval& t) { return {(t.lo + t.hi) * 0.5f, (t.lo + t.hi) * 0.5f}; }

float traceGlobe(Vec3 ro, Vec3 rd) {
    Interval t = {near, far};
    for (int i = 0; i < 32; ++i) {  // Sign change detection
        Interval t_mid = mid(t);
        Vec3 p = ro + rd * t_mid.lo;
        Interval d = sdf(p);  // Implicit SDF with interval arithmetic
        if (d.lo <= 0.0f && d.hi >= 0.0f) t = {t.lo, t_mid.hi};
        else t = {t_mid.lo, t.hi};
    }
    // Bisection refinement
    for (int i = 0; i < 20; ++i) {
        float t_mid = (t.lo + t.hi) * 0.5f;
        float d = sdf_scalar(ro + rd * t_mid);
        if (d <= 0.0f) t.hi = t_mid; else t.lo = t_mid;
    }
    return t.hi;
}
public final class SGAInterval {
    public final float lo, hi;
    public SGAInterval(float lo, float hi) { this.lo = lo; this.hi = hi; }
    public SGAInterval mid() { return new SGAInterval((lo + hi) * 0.5f, (lo + hi) * 0.5f); }
}

public static float traceGlobe(Vec3 ro, Vec3 rd) {
    SGAInterval t = new SGAInterval(near, far);
    for (int i = 0; i < 32; ++i) {  // Sign change detection
        var tMid = t.mid();
        Vec3 p = ro.add(rd.mul(tMid.lo));
        var d = sdf(p);  // Implicit SDF with interval arithmetic
        if (d.lo <= 0 && d.hi >= 0) t = new SGAInterval(t.lo, tMid.hi);
        else t = new SGAInterval(tMid.lo, t.hi);
    }
    // Bisection refinement
    for (int i = 0; i < 20; ++i) {
        float tMid = (t.lo + t.hi) * 0.5f;
        float d = sdfScalar(ro.add(rd.mul(tMid)));
        if (d <= 0) t.hi = tMid; else t.lo = tMid;
    }
    return t.hi;
}
public readonly record struct SGAInterval(float Lo, float Hi) {
    public SGAInterval Mid() => new((Lo + Hi) * 0.5f, (Lo + Hi) * 0.5f);
}

public static float TraceGlobe(Vec3 ro, Vec3 rd) {
    var t = new SGAInterval(near, far);
    for (int i = 0; i < 32; ++i) {  // Sign change detection
        var tMid = t.Mid();
        var p = ro + rd * tMid.Lo;
        var d = SDF(p);  // Implicit SDF with interval arithmetic
        if (d.Lo <= 0 && d.Hi >= 0) t = new SGAInterval(t.Lo, tMid.Hi);
        else t = new SGAInterval(tMid.Lo, t.Hi);
    }
    // Bisection refinement
    for (int i = 0; i < 20; ++i) {
        float tMid = (t.Lo + t.Hi) * 0.5f;
        float d = SdfScalar(ro + rd * tMid);
        if (d <= 0) t = new SGAInterval(t.Lo, tMid);
        else t = new SGAInterval(tMid, t.Hi);
    }
    return t.Hi;
}

8. Scripting Integration

Lua Integration (sol2)

# Lua via sol2 (C++ only - shown for reference)
# Python embedding alternative:
import lua54
from functools import partial

class ScriptSystem:
    def __init__(self):
        self.lua = lua54.new_state()
        self.lua.open_libs(lua54.base, lua54.math, lua54.table)
        self.entity_scripts = {}

        # Bind engine API
        self._bind_entity_api()
        self._bind_factory_api()
        self._bind_ecs_api()

    def _bind_entity_api(self) -> None:
        @lua54.function(self.lua)
        def entity_get(entity, comp_type):
            return ecs.get_component(entity, comp_type)

        @lua54.function(self.lua)
        def entity_add(entity, comp_type, *args):
            return ecs.add_component(entity, comp_type, *args)

    def _bind_factory_api(self) -> None:
        @lua54.function(self.lua)
        def spawn(prefab: str, pos: tuple) -> int:
            return factory.create_from_prefab(prefab, pos)

    def run_script(self, entity: int, script_path: str) -> None:
        script = load_script(script_path)
        env = self.lua.new_table()
        env["self"] = entity
        script(env)
#include <sol/sol.hpp>

class ScriptSystem {
    sol::state lua;
    std::unordered_map<Entity, sol::table> entityScripts;

public:
    ScriptSystem() {
        lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::table);

        // Bind engine API
        lua.new_usertype<Entity>("Entity",
            "get", &Entity::getComponent,
            "add", &Entity::addComponent,
            "destroy", &Entity::destroy
        );

        lua.set_function("spawn", [this](const std::string& prefab, Vec3 pos) {
            return factory.createFromPrefab(prefab, pos);
        });

        lua.set_function("find", [this](const std::string& tag) {
            return ecs.findByTag(tag);
        });
    }

    void runScript(Entity e, const std::string& scriptPath) {
        auto script = loadScript(scriptPath);
        sol::environment env(lua, sol::create, lua.globals());
        env["self"] = e;
        script(env);
    }
};
// LuaJ integration
import org.luaj.*;
import org.luaj.lib.*;

public class ScriptSystem {
    private final Globals globals = JsePlatform.standardGlobals();
    private final Map<Entity, LuaTable> entityScripts = new HashMap<>();

    public ScriptSystem() {
        globals.load(new BaseLib());
        globals.load(new MathLib());
        globals.load(new TableLib());

        // Bind engine API
        globals.set("spawn", new TwoArgFunction() {
            public LuaValue call(LuaValue prefab, LuaValue pos) {
                return LuaValue.valueOf(factory.createFromPrefab(prefab.checkString(), toVec3(pos)));
            }
        });

        globals.set("find", new OneArgFunction() {
            public LuaValue call(LuaValue tag) {
                return LuaValue.valueOf(ecs.findByTag(tag.checkString()));
            }
        });
    }

    public void runScript(Entity e, String scriptPath) {
        LuaValue script = globals.loadfile(scriptPath);
        var env = new LuaTable(globals);
        env.set("self", LuaValue.valueOf(e.getId()));
        script.call(env);
    }
}
// MoonSharp integration
using MoonSharp.Interpreter;

public class ScriptSystem {
    private readonly Script _lua = new Script(CoreModules.Preset_HardSandbox);
    private readonly Dictionary<Entity, Table> _entityScripts = new();

    public ScriptSystem() {
        _lua.Globals["spawn"] = (Func<string, Vec3, int>)((prefab, pos) => 
            Factory.CreateFromPrefab(prefab, pos));

        _lua.Globals["find"] = (Func<string, int>)(tag => 
            Ecs.FindByTag(tag));
    }

    public void RunScript(Entity e, string scriptPath) {
        string script = File.ReadAllText(scriptPath);
        Table env = new Table(_lua);
        env["self"] = e.Id;
        _lua.DoString(script, env);
    }
}

9. Editor Architecture

ImGui-Based Editor

import imgui

class Editor:
    def __init__(self, ecs):
        self.ecs = ecs
        self.show_inspector = True
        self.show_asset_browser = True
        self.show_profiler = False
        self.selected_entity = None

    def draw(self) -> None:
        self._draw_menu_bar()
        if self.show_inspector: self._draw_entity_inspector()
        if self.show_asset_browser: self._draw_asset_browser()
        if self.show_profiler: self._draw_profiler()
        self._draw_viewport()

    def _draw_entity_inspector(self) -> None:
        imgui.begin("Inspector", lambda: setattr(self, 'show_inspector', False))
        if self.selected_entity:
            imgui.text(f"Entity: {self.selected_entity}")
            for comp_type, comp in self.ecs.get_components(self.selected_entity).items():
                if imgui.collapsing_header(comp_type.__name__):
                    self._reflect_draw(comp)
            if imgui.button("Add Component"):
                imgui.open_popup("AddComponent")
            self._draw_add_component_popup()
        imgui.end()

    def _draw_viewport(self) -> None:
        imgui.begin("Viewport")
        # Render to offscreen texture, display as ImGui::Image
        # Gizmo interaction (ImGuizmo)
        if self.selected_entity:
            self._draw_gizmo(self.selected_entity)
        imgui.end()
class Editor {
    bool showEntityInspector = true;
    bool showAssetBrowser = true;
    bool showProfiler = false;
    Entity selectedEntity = INVALID;

public:
    void draw() {
        drawMenuBar();
        if (showEntityInspector) drawEntityInspector();
        if (showAssetBrowser) drawAssetBrowser();
        if (showProfiler) drawProfiler();
        drawViewport();
    }

    void drawEntityInspector() {
        ImGui::Begin("Inspector", &showEntityInspector);
        if (selectedEntity != INVALID) {
            ImGui::Text("Entity: %llu", selectedEntity);

            // Auto-generate from component reflection
            for (auto& [type, comp] : ecs.getComponents(selectedEntity)) {
                if (ImGui::CollapsingHeader(type.name())) {
                    reflectDraw(comp);  // Auto-UI from reflection
                }
            }

            if (ImGui::Button("Add Component")) {
                ImGui::OpenPopup("AddComponent");
            }
            drawAddComponentPopup();
        }
        ImGui::End();
    }

    void drawViewport() {
        ImGui::Begin("Viewport");
        ImVec2 size = ImGui::GetContentRegionAvail();
        ImGui::Image(renderTargetSRV, ImVec2(size.x, size.y));
        // Gizmo interaction (ImGuizmo)
        if (selectedEntity != INVALID) {
            drawGizmo(selectedEntity);
        }
        ImGui::End();
    }
};
// ImGui Java bindings
import org.imgui.*;

public class Editor {
    private boolean showInspect = true;
    private boolean showAssets = true;
    private boolean showProfiler = false;
    private Entity selected = null;

    public void draw() {
        drawMenuBar();
        if (showInspect) drawEntityInspector();
        if (showAssets) drawAssetBrowser();
        if (showProfiler) drawProfiler();
        drawViewport();
    }

    private void drawEntityInspector() {
        ImGui.begin("Inspector", () -> showInspect = false);
        if (selected != null) {
            ImGui.text("Entity: " + selected.id());
            for (var entry : ecs.getComponents(selected).entrySet()) {
                if (ImGui.collapsingHeader(entry.getKey().getSimpleName())) {
                    reflectDraw(entry.getValue());
                }
            }
            if (ImGui.button("Add Component"))
                ImGui.openPopup("AddComponent");
            drawAddComponentPopup();
        }
        ImGui.end();
    }

    private void drawViewport() {
        ImGui.begin("Viewport");
        ImVec2 size = ImGui.getContentRegionAvail();
        ImGui.image(renderTargetSRV, new ImVec2(size.x, size.y));
        if (selected != null) drawGizmo(selected);
        ImGui.end();
    }
}
using ImGuiNET;

public class Editor {
    bool _showInspector = true;
    bool _showAssets = true;
    bool _showProfiler = false;
    Entity? _selectedEntity = null;

    public void Draw() {
        DrawMenuBar();
        if (_showInspector) DrawEntityInspector();
        if (_showAssets) DrawAssetBrowser();
        if (_showProfiler) DrawProfiler();
        DrawViewport();
    }

    void DrawEntityInspector() {
        ImGui.Begin("Inspector", ref _showInspector);
        if (_selectedEntity != null) {
            ImGui.Text($"Entity: {_selectedEntity.Id}");
            foreach (var (type, comp) in _ecs.GetComponents(_selectedEntity)) {
                if (ImGui.CollapsingHeader(type.Name)) {
                    ReflectDraw(comp);
                }
            }
            if (ImGui.Button("Add Component"))
                ImGui.OpenPopup("AddComponent");
            DrawAddComponentPopup();
        }
        ImGui.End();
    }

    void DrawViewport() {
        ImGui.Begin("Viewport");
        var size = ImGui.GetContentRegionAvail();
        ImGui.Image(renderTargetSRV, new System.Numerics.Vector2(size.X, size.Y));
        if (_selectedEntity != null) DrawGizmo(_selectedEntity);
        ImGui.End();
    }
}

10. Build Pipeline

# build.yaml
platforms: [windows, linux, macos, android, ios]

configurations:
  debug:
    defines: [DEBUG, TRACY_ENABLE]
    sanitize: [address, undefined]
  release:
    defines: [NDEBUG]
    optimize: speed
    lto: full
  shipping:
    defines: [NDEBUG, SHIPPING]
    optimize: speed
    lto: full
    strip: true

asset_pipeline:
  textures:
    - format: BC7 (desktop), ASTC (mobile)
      mipmaps: true
      compress: true
  meshes:
    - optimize: meshopt
      quantize: [position: 16, normal: 8, uv: 16]
      generate_tangents: true
  audio:
    - format: Opus (music), ADPCM (sfx)
      sample_rate: 48000

packaging:
  windows: [exe, dlls, assets.pak]
  linux: [elf, .so, assets.pak]
  macos: [.app bundle]
  android: [.aab, .apk]
  ios: [.ipa]

Resources

Books

  • Game Engine Architecture โ€” Jason Gregory (3rd ed.)
  • Real-Time Rendering โ€” Akenine-Mรถller, Haines, Hoffman
  • Physics for Game Developers โ€” David Bourg, Bryan Bywalec
  • Game Programming Patterns โ€” Robert Nystrom
  • Mathematics for 3D Game Programming โ€” Eric Lengyel

Open Source Engines (Study)

Engine Language Notable For
Godot C++/GDScript Node-based, open source
Bevy Rust ECS, data-driven
Unreal C++ Industry standard, source available
O3DE C++ Modular, AWS-backed
bgfx C++ Cross-platform renderer
EnTT C++ Fast ECS
flecs C ECS with queries

Papers & Talks

  • "A Frame Graph for Frame Graph" โ€” Frame graph architecture
  • "GPU-Driven Rendering Pipelines" โ€” SIGGRAPH 2016
  • "Interval Arithmetic for Ray Tracing" โ€” Related to SGA Globe
  • "Continuous Collision Detection" โ€” Physics
  • "Parallel Game Engine Architecture" โ€” GDC talks

Summary: Engine Layers

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              GAME LOGIC                     โ”‚  (Your game code)
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚           ENGINE RUNTIME                    โ”‚
โ”‚  ECS โ”‚ Physics โ”‚ Renderer โ”‚ Audio โ”‚ Script โ”‚  (Systems)
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚            CORE INFRASTRUCTURE              โ”‚
โ”‚  Job System โ”‚ Memory โ”‚ Assets โ”‚ Config      โ”‚  (Foundation)
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚       PLATFORM ABSTRACTION LAYER            โ”‚
โ”‚  Window โ”‚ Input โ”‚ GPU โ”‚ Audio โ”‚ FS โ”‚ Net    โ”‚  (Platform)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Rule of thumb: The engine should make the common case fast and the complex case possible. Don't over-engineer โ€” build what your game needs, refactor when patterns emerge.