Why Game Devs Don't Merge Files

6 hours ago 2

In the world of gamedev, versioning mistakes can be expensive and irreversible. Two people working on the same character texture, complex material, or Unreal Blueprint can throw away days of work if they don’t realize both are editing the same file. In the world of business software, which I’ll refer to as Text World, merging two files is usually barely an inconvenience. In the world of multimedia assets, which I’ll refer to as Binary World, it’s practically impossible. This reality ultimately changes the workflows, rituals, and habits of the development team in ways that Text World people may never encounter.

I’ll walk you through the key differences between the broader software version control and how game studios actually manage the chaos of binary assets: from version control systems and asset locking, to CI/CD pipelines, massive file sizes and human coordination rituals.

Binary files, so many binary files

Unlike a traditional web app, a utility mobile application, or most forms of business software, a modern video game is a marvelously complex multimedia system. It blends dozens of asset types—animations, audio, models, materials, logic—meticulously stitched together and projected to the player’s eyes and ears hundreds of times per second. All of it runs in real time, often across the network, many times on disparate hardware, to deliver an experience that’s not just functional, but also deeply moving.

Anyone who’s played a title like Journey can tell you how surprisingly personal it felt. How a few hours with no dialogue and almost no UI left them feeling something transcendent they couldn’t quite put into words. I’m not convinced any other medium is able to deliver experiences this compelling. Anyway. I digress.

Animations, audio, video, visual effects, 3d models, images, materials, AI logic, data files describing stats of innumerable items and abilities, and oh so many others. The majority of these files are in a binary format, a complete reversal from business software, which is primarily made up of text-based code and data.

Of course, code is still omnipresent in a game project, often written in a compiled, close-to-the-metal language like C++. On top of that, you also “code” higher-level logic in one of the many scripting languages supported by your specific game engine. These languages conveniently abstract the gnarly internals of the system to designers and artists who are less interested in the architecture and performance of the underlying machine.

Often this scripting interface is made of text (C#, GDScript, Luau), but sometimes the engine designers decide to take a different set of trade-offs and go with a binary format instead. Unreal Engine’s Blueprint scripting is perhaps the most notorious one in the category.

A Quick Aside on Blueprints

There is much power to visually representing the flow of logic. Connect nodes together, step-debug through the edges of the graph as the game is running, see the connections light up as data flows through them. It’s a powerful paradigm. And the pain of not being able to use git or diff against text is eased with powerful built-in tools for tracking Blueprint history (until you rename the file, womp!) and for showing Blueprint diffs between versions. It’s a tedious manual process of diving through menus, but it’s possible, and it works. Automated or best-effort merging of Blueprints is not (yet?) possible however. You still need exclusive locks.

For UE-based projects, every early team will lean more on C++ or Blueprints depending on their team composition. Programmer-heavy teams and teams with IDE-eager technical designers will be more comfortable with logic living in native code. In contrast, teams without programmers might end up with the entirety of their game completed without a single added line of C++. Both approaches have significant tradeoffs, as one might expect, but both have been shown to work in real-world settings.

I wonder why there aren’t more Blueprint-like tools out there in the broader software world. Is it because most problem spaces are served fine with a text-based interface for building logic? Is it because most companies don’t have the funding and the long term outlook of an Epic Games to iterate on such a tool?

Programmers Play Bass

The natural consequence of the multimedia nature of a game, influenced by the genre and development stage, is that programmers are often the minority of the project’s team. Most of the hires are artists, responsible for delivering an immense amount of carefully hand-crafted assets for the project. Assets that are either plugged directly into the interfaces provided by the engine or into abstractions built on top of those by programmers to make artists’ lives easier. With the 2025-era resistance towards AI-generated art and the positive disposition towards LLM-generated code among most programmers, the ratio of artists to programmers is likely to continue growing.

Programmers play a support class in games, with the stars of the show being the designers and artists who move the player emotionally and deliver a compelling, visceral interactive experience. The programmer is there to help deliver the emotional payload as efficiently and pain-free as possible, making their vision come to reality within the agreed-upon frame budget.

Given that most game projects are driven by artists, the prevalence of workflows centered on binary files is not surprising. Carefully merging lines of text together from different versions of a file is not a native experience for a designer of 3d models, sound effects, or textures. Unlike Text World, this is one of those cases where it makes sense for programmers to accept the needs of the majority of the project and take the back seat, rather than having everything revolve around programming workflows. Some teams try to strike “the best of both workflow worlds”, as we’ll see below, but the jury is out on whether that ends up being a net win.

It’s a Perforce’s World

Perforce dominates source control in the mid-to-large commercial gamedev world. Some studios use alternatives like Git + LFS or Plastic SCM, but most stick with Perforce because it’s the default supported by engines like Unreal out of the box. This spares most of the additional tax of having to support an off-the-beaten-path system that engine developers, such as Epic Games themselves, don’t officially use or test extensively against. For example, many version control features in Unreal Engine work out of the box only if you’re using Perforce.

The Perforce model is that of a central server, the authoritative source of truth, from which every developer fetches their files and pushes them back up to. You download the latest version of each file, unlike Git, where you are getting the whole history at once. The central server’s history can be absolutely massive, and, likely, you wouldn’t be able to fit that on your drive. Unlike Git, there’s no common practice of syncing one local workspace against multiple remotes. Distributed workflows are uncommon, largely because merging binary assets is practically infeasible.

The key aspect of this is that the central server serves as an arbiter of file access. With this central authority, it becomes possible to police who gets access to what file within the Perforce depot, which is the key piece of functionality needed to work with binary files as a team.

If you absolutely insist on not using Perforce, you can still use plugins and other third-party tooling for Git support, but you will be on your own, and inexplicable VCS errors are now your responsibility. Some teams are happy with it and will power through, but most artists in the industry are already used to the Perforce workflow. This is reminiscent of Salesforce in the sales world: a dominant, aesthetically-offensive-but-familiar tool that no one loves but everyone tolerates. A tool that brings the benefit of not having to train your staff on yet another mostly-similar system that will require extra training time and lead to mistakes and confusion instead of more sales.

For most artists, Perforce is already a well-developed muscle, and its workflow makes intuitive sense. One of the key takeaways from working on a multidisciplinary project is realizing just how different workflows can be on the same project and how easy it is to lack empathy for what would make an optimal workflow for someone else on the team who performs a different role than you do. Business software has its own share of disciplines, but it’s not even in the same ballpark as in game development, as delightfully captured in The Door Problem.

During my time at Freckle, we had a fairly sizable curriculum design team, in charge of building thousands of questions to test K-12 students on their knowledge of math, grammar, social studies, and other subjects. The content would go into the main Freckle product, acting effectively as the “game engine”. However, even that team was based entirely on CSVs and YAML files, most of which, again, they were able to version in Git.

Innovating in this area seems generally not worth it. Perforce is not delightful, an artifact of a different era—I checked, it’s actually from 1995, wow!—and the interface shows it. But once you get used to it, it’s like any other piece of software, possibly better than most others for the job, as it has undergone decades of fixes and has had hundreds of thousands of eyeballs on it, triggering every possible accidental corner case.

Matters of Size

Speaking of areas where Perforce again shines, it’s hard to overstate just how gargantuan in size modern 3D game projects get. A single 4k texture can take up 50MB or more, and you end up with hundreds or thousands of those alone, and that’s just for textures. It’s not uncommon for projects to span many terabytes, with some reaching tens of terabytes. Unreal Engine and its build artifacts alone can easily reach 150GB or more. Managing what a developer needs to download on their machine to complete their work that day becomes a real challenge as the project grows, as opposed to in business software, where a project (and its previous snapshots) can be compressed into a few hundred MB and downloaded from GitHub.

The good news is that the final payload shipped to customers is significantly smaller, thanks to a clever build process. You cook the assets to prepare them for the target platform, shrink them to the desired size and format, and ignore most assets that won’t actually be used in the game. You also employ other techniques to keep the final deliverable “small”. That’s why your download from Steam will often be in the 50-200GB range, rather than the terabytes the release build process initially began with.

The Lock Economy

Back to the central problem I alluded to earlier: binary assets cannot be merged if two people made edits to them at the same time (also known as a three-way merge).

What does it mean to merge a rendered music track? What does it mean to merge two PNGs together? What is the correct resolution flow for merging two 3d meshes into a final third one?

You could, in theory, conceive of tooling that would enable this, given the proper preservation of edit history. These tools would allow the developer to choose which edits to keep, what order to merge them in, and replay them; however, most of the time, the result would be either unusable or barely worth the effort. In the world of game development, binary assets are practically always checked out exclusively. Even something as essential to UE-based development as Blueprints only has a diffing tool; the merge itself is up to the developer, as automating it would be fraught with ambiguities that require more context than the asset provides.

The workflow with binary files is exclusive checkouts: a file is uniquely assigned to one developer, and nobody else gets permission to edit it—to avoid later predictable tears—while they’re working. This isn’t dysfunction, this is damage and disappointment prevention in a context where merges are simply not an option.

This is highly strange to someone coming from Text World. What do you mean, I get to hold up the rest of the team on a set of files? What if they need them? Can I hang onto them forever? How do I coordinate? What if they lock some of the files I need to access exclusively later, but to finish their work, they need to access some of the files I’ve already started editing? Time to perform a human-driven deadlock-induced rollback? Fun.

You’re now in human coordination territory; you need to anticipate this sort of issue when working on tasks. The larger the undertaking, the higher the chance you will need to lock files that someone else was working on, which means they’re now stuck on their respective tasks, needing to work on something different until you return those files. Again, bizarre to someone coming from Text World.

Branches Are a Luxury You Cannot Afford

What is surprising to people coming from the world of text-based projects is how little reliance there is in games on topic branches for your workflow. Since you cannot merge most of the files you work with and other developers cannot work on them while you are, there’s no longer a point in having an isolated branch of the project in which to do your changes. The exclusive checkouts are accomplishing that.

The typical workflow goes from backward-integrating topic branches into main, either once or continuously, to always working in main. You simply check out a subset of the files, locking them for everybody else, and do your work.

The major downside is that now everyone else is blocked from proceeding with work on those files. In Text World, you can typically all edit at the same time, assuming one of the developers isn’t completely uprooting the module, and still successfully merge the changes in the end. Not always pretty, but usually tolerable. In Binary World, only one person gets to move forward; everybody else is stuck.

Many a holiday or weekend is spent trying to rush a sizeable change that is blocking several people on the team so that you can finally release those files back into the wild. Unreal Engine projects that heavily rely on Blueprints, UE’s native visual scripting language, for complex game logic are particularly susceptible to this issue. Programmer-heavy teams will port as much as possible to a native C++ implementation, which can then be three-way-merged. That, of course, comes at the cost of slower cycle times and non-editability for game designers, which may or may not be a price you want to pay.

Divide or Be Conquered

One unexpected silver lining of lock-driven version control is that it forces modules to stay small and decoupled, split up in such a way that allows developers to lock a specialized subset of functionality, rather than a giant, monolithic file.

In Unreal Engine, the bottleneck of every character-based game reliant on Blueprints is the infamous ACharacter subclass, which practically every game implements for their main player character. This class tends to bloat with every imaginable piece of functionality, including player inventory, animation, attributes, movement, skills, and communication with other players, among others. It’s a quick and dirty place to park your logic and make it readily available for experimentation.

The naive approach requires the Blueprint to be checked out exclusively for most gameplay changes, but only one developer can edit it at a time. Now changes have to be small, or the file is locked for an uncomfortably long time. Or all changes to that class need to be assigned to one single developer in a “single threaded queue” of work, avoiding issues of coordination, but also losing team efficiency.

This is where the well-trodden Actor Component system comes to the rescue. It allows the functionality of an Actor class, such as the player character, to be contained in small modules that can be attached to any other Actor that needs them. Now, changing a specific piece of player functionality only requires a change to that module. The main character class can finally be left alone.

The main counter-argument to this approach is that it adds an extra level of indirection for something that might as well be in the same module if it’s only ever used by one from one location. “Why did we move health logic into its own module if the only thing using it is the player? We could just keep it there and not have to jump to another file.” Unblocking developers will typically win the cost/benefit analysis.

The Human Protocols

Lock-based workflows often introduce strange rituals, primarily related to coordination and social etiquette.

The most basic one: remembering to release your locks. It’s easy to forget, especially if you were just tinkering with a file. But now it’s 11pm on a holiday weekend, and someone on the other side of the world is blocked. All they can do is ping you and hope you’re awake.

Over time, you develop habits: releasing any files you’re not actively working on before signing off, especially if they’re likely to be needed by someone else. On globally distributed teams, this becomes particularly challenging. The person who locked the file might be asleep when you realize you need it. Your options now are to stop work entirely or make a copy and hope you can manually port your changes over later. Not fun.

Sometimes it’s negotiable. The person holding the lock might say, “My change doesn’t matter, just overwrite it.” Perhaps it was a minor config tweak or a cosmetic adjustment. With their blessing, you work on the unlocked version and clobber their edits when you submit. It’s messy, but manageable.

Over time, implicit ownership patterns form. With maturity and scale, they become more formalized. The audio person owns the ambient audio system. The animation lead owns the blend trees. The systems programmer owns the inventory logic. They’ll often keep “their” files locked for days (sometimes weeks), and no one minds.

Other files become hot zones. High-traffic Blueprints. Shared config assets. You learn which ones are sensitive because you find yourself asking, “Hey, are you still working on X?” often enough. Eventually, a little voice in your head starts chiming in: “Do I really need to hold this file much longer? Maybe I should release it. I bet Dave’s going to need it tomorrow based on what’s in Linear.”

Big CI/CD Energy

Given all of the above, how do you actually test every submission to the project? With the size and volume of assets involved, it’s anything but trivial.

You can’t just spin up a fresh CI dyno and pull several terabytes of data every time you want to run a build. Instead, each CI machine hosts a long-lived Perforce workspace. With every build, you sync that workspace to the latest commit instead of redownloading everything. Of course, now you’ve introduced a new problem: if that workspace’s state gets corrupted, you’re in for a bad day.

Then there’s the horsepower problem. A full build of a AAA-scale Unreal project can take hours, even on beefy hardware. And like in every other software domain, slow feedback is deadly. If your change subtly breaks the build, every developer behind you gets stuck waiting. The faster you find out, the better.

Setting up CI/CD for Unreal isn’t as easy as adding a few GitHub Actions lines to a Rails app. We didn’t want to deal with the hassle, so we used a third-party provider built specifically for UE. The people at Runreal run a fleet of high-end bare-metal machines with preloaded depots, ready to sync and build at a moment’s notice.

In web development, CI feedback on a commit usually takes a few minutes. In modern high-fidelity games, that’s rarely the case. Waiting an hour or more for a build run is typical. That’s why developers do as much local validation as they can before submitting a changelist.

Test automation in games is its own beast. Unlike business software, large 3D games rely heavily on manual QA. That means a lot of regressions slip through—especially the subtle ones you won’t notice for days. On small teams, developers often double as testers. Whether AI-driven QA will transform this in the next few years is an open question, but a lot of people are racing to find out.

Specialized test levels can help manually validate happy-path scenarios, but they’re no silver bullet. Testing a change in games takes far longer than in systems with full test coverage and a clean Test Pyramid. You can’t just toss a change at CI and trust that every edge case will be caught. Especially not when the goal is to preserve the right “feel” across a real-time, distributed system simulating a live multiplayer environment. One day, maybe.

The Bottom Line

As dominant as the Text World’s Git-based workflow is in the world of software development, it’s easy to forget that there are universes in technology that follow a fundamentally different approach to meet their distinct needs. What works in one field of software might not be viable in another.

There’s a lot to learn from the gamedev world: managing massive file depots, collaborating with experts who view the world through a different lens, and whose contributions take an entirely different route.

Gamedev doesn’t “do software wrong” or is behind the times. The field is working within the given constraints and uses solutions that work around the challenges unique to the craft. One could argue it is doing that as elegantly as current technology allows.

Disclaimer: The world of game development is vast, deeply rooted in computing history, and filled with countless unique niches beyond what I’ve described here. After all, you can deploy and control Doom on a printer. What is the best SDLC practice for that? This article offers a broad overview and doesn’t attempt to capture every possible environment, workflow, game genre, or team structure you might encounter in the industry.

Read Entire Article