Joël van der Werf
All posts

Games in their own repositories: separating Hydro from its first game

Hydro, its editor and its first game all lived in one repository. The plan to give every game its own repo, the 16 couplings in the way, and the first five steps.

Until now, Hydro, the Oxygen editor and Eonlore, the tactical RPG I'm building on it, all lived in one repository. The game's project sat inside the editor's folder: 234 MB in 6,795 files. That was convenient for a while, but it means the engine can't be cloned without the game, a second game has nowhere to go, and nobody can tell where the engine ends and the game begins.

The goal of the current pull request: a game lives in its own git repository, and the engine, editor and player know nothing about any one game. Oxygen opens or creates a project from any folder, through a project hub.

Defining "done" first

Before any code, the plan has a success test of five steps:

  1. Clone the engine alone and build it. Oxygen starts from its exe, from any working directory, and shows the project hub.
  2. New Project in a scratch folder creates a project that opens, saves and plays.
  3. Clone the game repo next to the engine and build its workspace. Its Oxygen opens the game and the Shrine Glade plays.
  4. File › Build writes a package into the game's own folder, and the package still runs with the engine checkout renamed.
  5. Nothing in the engine repo changed while doing 2–4.

Sixteen couplings

Then I listed every place where the engine assumed the game, checked against the code. Some examples:

  • Engine shaders were loaded by paths relative to the working directory, and lived in the editor's folder. Oxygen only worked when started from that exact folder.
  • Editor settings and the docking layout were found from the working directory, and tracked in git.
  • The game code was a fixed library linked into every Oxygen and Runtime build.
  • All 36 build-script paths were relative to the solution, so the engine couldn't be included from another repo.
  • And one surprise: Debug CI was red on main, because the generated reflection code exceeded the object file's section limit. That became step zero.

The decisions that matter

Workspace and project are different things

A workspace is the build environment of one game: its solution, its C++ code, its build scripts. A project is the content the editor opens: scenes, prefabs, data. Each game repo is a workspace with one project inside. The workspace is described in one JSON file that premake, Python, the batch scripts and CI can all read.

Which engine version does a game need?

The engine is checked out next to the game, and an engine.lock file names the Hydro commit the game expects. Locally a mismatch only warns, because engine and game are often changed together. In CI it fails, because CI checks out exactly that commit.

Opening the wrong game's project must be safe

I checked what the serializers do with types they don't know. Unknown script values and unknown data fields are kept and written back, but an unregistered component is dropped on save. So editing a project with another game's editor would silently destroy content.

That decided it: every project carries a stable game ID. Opening another game's project offers Open read-only, Locate the right editor… or Cancel, and there's no "continue anyway" until unknown components survive a round trip.

Resources travel with the exe

Every build copies the engine's shaders next to its exe, so a package never depends on the source checkout. Only Oxygen's Debug and Release builds may load from the source folders, so shader hot reload still edits the real files.

Rehearse, then split, and keep the history

The game first moves to Projects/Eonlore/ inside the engine repo, and the success test runs there. Only then is it split out with git filter-repo, following a map of every path game material has ever lived at. Nothing is deleted from the engine until git log --follow and git blame show pre-split history on four sample files: a scene, a script, a tool and a doc.

No Git LFS, decided from data

The whole repository pack is 245 MB and the largest file is 5.8 MB. LFS wouldn't change clone sizes meaningfully and adds tooling, so it's not worth it yet. Blender backup files get ignored and dropped from the extracted history instead.

Where it stands

StepStatus
S0 Fix Debug CI (/bigobj)Done
S1 Engine paths, shaders staged beside every exeDone
S2 Editor settings per user, out of gitDone
S3 Project hub and recent projectsDone
S4 The game as a parameter of the workspace, read-only for other gamesDone
Engine-owned test bed (Sample)Next
S5 Rehearsal inside one repoPlanned
S6 Split, with history preservedPlanned

Every step is one commit, and CI has to be green before the next one starts. What CI can't check is the editor UI itself, so each step also has a short manual checklist to run on Windows.

Oxygen with the Shrine Glade loaded from the separate Eonlore folder
After the split: Oxygen loads the Shrine Glade from the game’s own folder (see the console), not from inside the engine.

What I'd take from this

  • Write the success test before the plan. "Runs with the engine checkout renamed" catches every hidden path dependency in one go.
  • Check the code, not your memory. The serializer finding changed a decision from "warn" to "read-only".
  • Decide from measurements. LFS sounded right until the numbers said otherwise.
Older: Making an engine readable to AI agents

Building a game, an engine, or the tools around them?

I'm based in Tokyo and open to conversations about Unity, C++ and engine roles, C# and .NET software roles, and teams that want to put AI agents to work on real codebases.