Spaghetti Graveyard 2027

08/10/2026 A new Beginning: 

I usually start from a blank state about once a year. My thoughts on what the game engine should do have gotten ahead of what my current code can do. Going in and patching a whole new architecture to the working game would just be painful.

Lets talk architecture.

 All of the data for the game is stored in a global hierarchy, but I don't like the way its been thrown together.

The new hierarchy will look like:

home:
    global_data* global; //timekeeping, networking,inter-thread communication etc
    map_data* maps[];
    player_data* players[];



map_data:
    core_app* core;           //machinery for rollback and re-simulation
    ECS_app* ECS;             //entity containment system for buildings and units
    UI_app* UI;               //widgets (buttons, textboxes, etc), mouse
    viewport_app* viewport;   //graphics objects like buildings and units as seen by the graphics thread
    graphics_app* graphics    //data and code needed to draw anything to the screen

    
The map data is made up of apps. Each app inherits from a system that lives inside a library, but contains added data used by the runtime.

core_app:
     bbCore core;             //bbCore is a structure provided by the core library
     bbClockHandle clock;     //bbClockHandle is part of the clock library
     ...


graphics_app:
    bbTextures* textures;           // png files uploaded from hard drive
    bbSprites* sprites;             // individual images of units
    bbAnimations* animations;       //collections of sprites
    bbDrawfunctions* drawfunctions; //code required to draw an asset to the screen

//arrays of sprites/animations/compositions each paired with a drawfunction
    bbCompositions* compositions;   
    bbFonts* fonts;

//final pass for graphics calls, ensuring they’re in painter’s algorithm order.
//Contains functions that call shaders to do the actual rendering  

    bbDrawBufferFunctions* drawBufferFunctions; 

 
 The point is, the app can be extended by the runtime. The library supplies a data structure that it knows how to deal with, and the runtime injects callbacks that operate on the extra data.

That being said, I'd like to keep my options open. A different option would be to give every system a void* extra_data. This would make it easy to extend systems without having to recompile anything. I think I'll do both.

Hard Core Rock:

The core is the central kernel of the game engine. It is responsible for the deterministic simulation of the game world. It does this by creating instructions that are pushed to different stacks. An instruction is popped from one of these stacks and then the instruction's callback function is executed. There is one stack for instructions to execute during regular gameplay. One stack contains instructions needed to roll back those instructions. The third stack contains instructions needed to resimulate the game state after it has been rolled back.

I had implemented these stacks with a segmented pool and intrusive list, but after switching to using a segmented deque I've had a 6 times speedup on a rollback heavy workload. The tradeoff is that the order of adding and removing instructions to the stacks is crucial.

The rollback machinery has a purpose. When a player takes an action, a request is send to the server and back. When the message is received back it creates a timed event for the players character to interpret. The message is broadcast to every connected client, and if the act_time of the timed event is before the clients current simulation state the game does a rollback and rewinds the game state to before the event was supposed to be enacted. The game is then run forward normally, as if the event had been created after the core's simulation_time.

There are a few things I'd like to change. There will be multiple maps loaded and one core per map in the global data hierarchy, so the core and it's instructions will need to be aware of which map they belong to. Currently the core isn't even aware of the core_app, so if code needs data from there it has to include the entire data hierarchy. I'd like to avoid reaching into the data hierarchy because I consider the data hierarchy game-specific code and the core is an engine library.

I would like to be able to enact timed events speculatively. When the player takes an action, the request is still sent to the server, but then the client acts on the event immediately. If the message from the server denies or modifies the request, then a rollback is triggered and the game is restored to the state it would have been in if the client hadn't acted on the event.

Modding:

I'm not an artist and I couldn't hope to design an entire AAA game. I'd like to focus on engine features that will attract other developers. One idea I've been floating is graphics packs.

There is a lot of overlap between the RTS and RPG games I've been thinking about for my engine, what applies to RTS civilisations could apply to RPG monsters and characters. One idea I've had is letting the player install graphics packs. There would be a base context for graphics and a graphics context per player. At the start of the game, the game would load a graphics pack based on the theme of the map such as forest/ jungle/winter. It would then load a graphics pack for each player's civilisation. After that the player is free to upload their own graphics packs.

Loading a graphics pack just adds some extra data to a shared graphics pool and adds some extra entries into the relevant context/dictionary. When a unit is spawned, the game just searches the player's context/dictionary for the specific asset, and then if that fails it searches the base context. 

Scripting:

It would be nice for players to modify game behavior without the possibility injecting malicious C code, and without having to write their own simulate/rollback instruction pairs. I propose a scripting language built on top of the deterministic, rollback-able core. 

The script would be implemented with three classes: listeners, nodes and variables.

Listeners wait for events such as events coming from outside the scripting system, timers, or changes to variables. When a listener receives an event, it notifies a node. The node runs a script then modifies variables. When a variable is modified, more listeners can be alerted. 

When a script is loaded is sets up:

Static Variables: shared between nodes in within the script

Input Variables: Interface coming in from other scripts

Output Variables: interface exposed to other scripts

Nodes: Nodes can contain their own local data, or shared data between similar nodes

Listeners: clocks, external events, variable changes

Scripts can be uploaded to namespaces and contexts, for example:

Import archer.bb as archer to players[player1] 

If one event is received by multiple listeners, listeners will need to be alerted in a deterministic order, but I haven't decided on what that ordering should look like. Loading scripts should probably be done in a deterministic order as well so maybe listeners are ordered by file then line number of the script that spawned them.

It will be interesting to make a kind of linker to link input and output variables between scripts.

There exists a global context and one for each player/AI bot. The global context can contain things like game rules, map-specific rules, victory conditions etc. A player context would be for things like tech tree and user interface. An AI bot would have scripts loaded for the tech tree, marco management and micro mangement.

That's enough for now.

Boston. 

Comments

Popular posts from this blog

Maths Game / Engine

Fossicking

Reina (Dungeons and Dragons Character)