matthewedmondson.com
Matt Edmondson
Producer · Game Developer · Software Engineer · Melbourne, Australia
Notes on game production, engineering, and the places they meet. More about me →
Writing
-
Every Branch, Every Sample
In Synthr every module computes its full effect on every sample, even when it's switched off, and the bypass is a multiply by zero. That looks wasteful, and on average it is. In a real-time audio callback the average isn't the number that matters.
-
A Direct and Empowering Coaching Style for Engineering Teams
Why I lead with recognition, follow with pressure, and try to give the people I coach authority over their work rather than just instructions about it.
-
The Engineer-Producer Dual Role: A Replayable Founding Pattern
An engineer-capable lead producer takes on hands-on engineering at the founding stage because the project demands more than the bench can supply, then moves back to producer-at-scale once the team is deep enough. I've now run this arc twice.
-
Zero-Trust for Unreal Development
Every change reaches main through the same gate (PR, review, CI), with review scaling to risk and the failsafe itself subject to review. The principle, the threat model, and how the approach actually works inside an Unreal studio.
-
Branching Strategies for Game Teams: Small vs. Large
Git branching strategies that scale with team size, from 2-person indie studios to 30+ developer teams, including binary asset workflows and the Perforce hybrid approach.
-
Comparing Two Custom Game Engine GUI Systems
A follow-up to the tour of my second engine's GUI: the same problems, solved again two years later in the Carbon Monoxide engine, and then refined for a decade. Where the old system managed slicing, reentrancy, and mouse capture with discipline, the new one manages them with structure, and the comparison shows which ideas survived by being copied and which survived by being re-learned.
-
Static Analysis Is Not a Weaker Kind of Code Review
Before anyone opens a pull request in my open source repositories, 306 static analysis rules have already returned a verdict, 94 of them security rules, every one of them reproducible and free. The habit of asking a language model to review code instead is not a tooling preference, it is a decidable question being re-asked in a form that cannot be decided, and it is most attractive to the operators least equipped to notice what it missed.
-
A Data-Driven GUI System in a Custom C++ Engine
The GUI system in my second engine is a small web browser: screens are text pages, a stylesheet restyles elements by copying prototype objects with the assignment operator, buttons can be hyperlinks, and a missing page renders a 404. A tour of how it works, why the god-object base class is the load-bearing decision, and which ghosts are still in the code.
-
You Don't Want Randomness, You Want Reproducibility
The same decision turns up three times across twenty years of my code: a C++ engine carrying its own rand(), a static site generator carrying mulberry32, and a PHP card server reseeding from the clock on every request. Two are right and one is worse than doing nothing, and the difference is not about randomness.
-
Three Hundred Files
Part 5 · The Rebrand That Left Both Versions in Place
3dsmaxforum became digitalartsfront in a full rewrite, and the previous version's files were left in the directory beside the new ones, referenced by nothing. Underneath the rebrand sat a self-serve advertising platform with campaign caps, frequency capping and PayPal billing.
-
Three Hundred Files
Part 4 · Two Scores, One for Making and One for Being Seen
The community kept two separate numbers per member. Participation counted what they made, weighted by effort. Popularity counted how others responded. Keeping them apart is the design decision, and the weights are four integers in a cron job.
-
Three Hundred Files
Part 3 · A Socket Server in PHP, Restarted by Cron
The live chat on a 2008 art community ran on a non-blocking socket server written in PHP, with heartbeats, a pid lockfile, and cron as its supervisor. The daemon harness under it was written for a VoIP softphone, and the commented-out entries are still in the launcher.
-
Three Hundred Files
Part 2 · The Search Engine Is a Table of Words
A hand-written inverted index in PHP and MySQL: one table mapping words to ids, another holding word, document and count. The interesting decision is what counts as a document, because a forum thread and every reply under it are indexed as one.
-
Three Hundred Files
Part 1 · No Forum Software
A 3D art community I built and ran from around 2007 to 2016, recovered from a web host backup. Over three hundred PHP files, sixty-two database tables, and no forum package anywhere near it. This is what was in there and how the order of it was worked out.
-
Thirty Seven Percent of the Time, You See Nothing
There is a class of defect that a test plan cannot find and production cannot avoid. The argument is arithmetic rather than anecdote: a one percent failure survives a full day of testing about as often as it gets seen once and closed as unreproducible, and the same defect reaches ten thousand people a day at a million users.
-
Deciding Between Failing Fast and Carrying On
Meeting data that should not exist, the argument is usually crash versus carry on. That is the wrong axis. Both can be loud and both can be quiet, and the one thing never defensible is continuing silently, which is what a codebase does when nobody chooses at all.
-
Branching and Three-Way Merge in a Browser-Based Data Editor
DataTool, a personal prototype of a browser-based editor for game design data from 2016, later became the basis for a tool adopted by a game team of about 60 developers. Recovered from a web host backup, it puts branches, three-way merges, per-cell conflict resolution and undo on database rows, and creating a branch inserts one row and copies nothing.
-
The Game Jam Starts Two Weeks Early
Five Global Game Jams in version control, 2014 to 2018. The first commit lands ten to fifteen days before the weekend in every year after the first, and none of that early work is gameplay. It's the repository, the engine, the build and the installer, so that five to seven people can start writing a game at hour zero.
-
Subtractive Dungeons, Additive Terrain
Every generator I've written turns out to be doing one of the two things a synthesizer does: starting with too much and filtering it down, or starting with nothing and summing contributions. In one case the correspondence isn't an analogy at all, because fractal terrain and additive synthesis use the same word for the same arithmetic.
-
Moving Is a Lean Plus a Wiggle
A fruit merging game where the character has no animation data. Everything it does is sine accumulators and one multiplier: a lean while moving, a decaying wobble on stopping whose frequency slows as it dies, and a juggle arc built from a quarter sine handed off to a quarter cosine.
-
Five Layers Moving at Different Speeds
A procedurally drawn cartoon eye that fakes a sphere out of flat layers by moving each one a different fraction of the gaze vector. The highlight stays where the light is, the eyelid closes along a curve, and one shape-building trick produces both the lid and a capsule.
-
The Source Control Heartbeat: Caching Branch State for Unreal Editors
An Unreal editor with source control enabled asks the same question every thirty seconds, on every workstation, and the people on the worst connections pay the most for it. Answering it once, next to the forge, turns out to be a small service with a lot of sharp edges around authorization and correctness.
-
Caching Git LFS: Why the Bytes and the Locks Want Opposite Homes
A caching proxy for the Git LFS HTTP API turns out to have two separate jobs with contradictory deployment requirements. Object bytes are bandwidth-bound and belong near the client. Lock listings are round-trip-bound and belong near the forge. One deployment can't be right for both.
-
Pitch, Pitch Class, and Note Are Three Different Types
Music theory is full of values that look like the same integer and mean different things. Modeling it in C# made the case for semantic types better than file paths or physical quantities did, and showed exactly where a type system stops helping and a scored guess has to take over.
-
Why JsonRequired Can't Depend on Another Property
System.Text.Json decides whether a property is required before it reads any JSON, which is why a requirement conditional on a sibling value can't be expressed with the built-in attribute. Working around it means a converter, and the converter has to buffer, re-materialize, and take its member model from the serializer rather than from reflection.
-
Generate Too Much, Then Delete
A dungeon generator from 2012 that never tries to build a correct dungeon. It places too many rooms, connects every pair it can, then removes rooms and doors one at a time while a pathfinder confirms each removal is safe. Correctness comes from the deletions, not the construction.
-
Five Notes That Can't Be Played Wrong
A rhythm game from a twelve hour jam that composes its own music. The generator contains no music theory at all. Every constraint lives in which five sounds were recorded, so an unguarded random pick can't produce a wrong note, and the note it picks is also the thing the player has to hit.
-
Noise as an Event Generator
A glitch post-process built from one idea: sample smooth noise along time, then threshold it. Below the threshold nothing happens, above it the effect fires with a magnitude the noise already shaped. Occasional, organic bursts from two texture samples and no state.
-
Keyframes in Source Code
An RTS where every building animates and none of the animation is authored in a tool. Keyframes are C++ calls at normalized times, the period is one number, animations cycle by construction, and tuning means a recompile.
-
Inside a 1,300-Line Scripting Language
A close read of my engine's scripting system: a line-based interpreter over a third-party expression parser, a binding layer where C++20 concepts derive what's scriptable from a variant, execution state small enough to be a vector of tuples, and suspension implemented as the absence of an action.
-
Forty-Six Bullets Dragged onto a Form
The oldest game code I still have is a Visual Basic 6 shooter from August 2004, written at seventeen in a rural town of a hundred people. The bullet pool is forty-six shapes dragged onto a form, GDI is declared by hand, and the changelog records the exact moment I found out about double buffering.
-
Every Widget Knows How to Arrive and Leave
A UI motion system where animating in and animating out are properties of a component rather than code somebody writes. Three animatable channels, each with a delay, a duration, a target and an easing curve, cubic-Bezier curves behind names, and one X-macro list generating the enum and the dispatch table.
-
A Synthesizer in a Garbage-Collected Language
A modular synth written in C#, where the audio callback takes a lock, allocates on the heap, and dispatches to the thread pool. Every one of those comes from a correct instinct expressed with the nearest available tool, in the one domain that punishes it hardest.
-
A Galaxy That Is a Pure Function
An infinite starfield where the chunk's identifier is the seed for its own contents, so four billion chunks of stars exist without any of them being stored. The ID is a packed coordinate, the mapping is done in double because a galaxy exceeds float, and the chunk store is a sorted vector.
-
Packing Assets in the Order They're Needed
A compressed asset pack can only be read forward. Seeking backward means decompressing the whole file again from byte zero, so the order assets sit in the file decides how much work loading costs. An explicit manifest, ordered by first use, turns startup into a single forward pass.
-
Wait States Are the Whole Point
Entity behavior is sequential in intent and awkwardly parallel in implementation. A scripting language whose defining feature is that a script can suspend mid-loop and resume next frame closes that gap, and the scoped binding model decides what each script is allowed to touch.
-
A Particle Is Eleven Numbers
The particle system I sent to EA as a portfolio piece in 2013. Eleven values per particle, two of them random seeds that stand in for everything varied, a shared interpolation curve baked into a lookup table, five force types with three application modes, and a ring buffer that never allocates.
-
One Schema, Six Implementations
A JSON schema declaring a class and its typed members generates the C++ class, both directions of serialization, deep copy, deep equality, lifetime, and an editor panel. Thirty-five declarations produce nine thousand lines, and the editor for a new content type costs nothing.
-
Tools That Share the Runtime
Content tools went from five separate editor executables to a tool framework running inside the game itself. The move removed the export format that could drift, put authoring in the same renderer the player sees, and made tool state something the engine persists rather than something each tool reinvents.
-
Two Anchors and a Touch Rect: Relative UI Layout
A layout system where every element declares which point on its parent it attaches to and which point on itself does the attaching, resolves against the aspect ratio at query time, and keeps the region it draws separate from the region it can be touched.
-
Best-Fit Atlas Packing and the Cost of the Free List
A texture atlas allocator is a memory allocator in two dimensions, and the same choices apply. Best-fit packing with an early exit on an exact match, a one-pixel border that exists for a reason nobody guesses first, and a free list that was a linear scan for years longer than it should have been.
-
Time-Sliced Asset Streaming Without Threads
A streaming loader that runs entirely inside the frame loop, advances a bounded amount of work per frame, and tunes its own I/O budget against a wall-clock measurement. No threads, no locks, and a failure mode that degrades instead of stalling.
-
The Renderer That Draws Nothing
An engine with three render backends, one of which accepts every call and draws nothing. The null implementation costs 274 lines, proves the abstraction is real, and buys the ability to run the game with no GPU at all.
-
Four Engines, Fifteen Years
Part 5 · What the Numbers Show
Putting four custom C++ engines side by side, a similar volume of code spread across six times as many files, 603 globals fell to none, and every improvement took a rule out of convention and handed it to something that enforces it.
-
Four Engines, Fifteen Years
Part 4 · Generating the Game Instead of Writing It
A decade after it shipped a mobile puzzle game, the same engine now runs a dungeon crawler where 9,315 lines of C++ are generated from 35 JSON files, entity behavior lives in a custom scripting language, and the engine is a submodule rather than a folder.
-
Four Engines, Fifteen Years
Part 3 · The Rewrite That Mobile Forced
Porting a shipped PC puzzle game to iOS meant a third engine rather than a port. Mobile turned three things that had been constants into data: screen resolution, application lifecycle, and how much of the codebase belongs to somebody else's SDK.
-
Four Engines, Fifteen Years
Part 2 · Twenty-Four Projects: Decomposing an Engine
The second engine answered the first one's problem by splitting into twenty-four projects with an enforced dependency graph. Average file size fell from 475 lines to 73. It also introduced sixteen manager singletons, which turned out to be global state with a nicer name.
-
Four Engines, Fifteen Years
Part 1 · 603 Globals and a Texture Streamer
The first engine I shipped a commercial game on started as someone else's OpenGL tutorial basecode and kept its state in a header with 603 global declarations. It also had a self-tuning, time-budgeted texture streamer writing into mapped GPU memory. Both things are true, and the second one explains the first.
-
Set Semantics and Deferred Execution in LINQ Except
A close look at how LINQ's Except works, why it never delegates to the collection's Contains, and how that changes when to materialize a query and which container to use.
-
AI-Assisted Coding vs Vibe Coding
Two fundamentally different ways to use AI for code. In one, the human is the author and AI accelerates the work. In the other, AI is the author and the human accepts output they may not fully understand. Both have a place. The real problem is not knowing which one is happening.
-
Documents as Bridges: Turning Pressure Into Structure
External pressure plus internal disagreement is the most common situation a producer faces. The approach I trust most is to turn it into a written document that frames the urgency and preserves the plan, so the argument happens against the document rather than against each other.
-
Crisis-Mode Composure: Authoring Postmortems, Not Pointing Fingers
Two infrastructure crises played out the same way. Stay calm, work the problem, communicate steadily, recognize the people who got through it alongside the team, and write the postmortem rather than pointing fingers. A producer's playbook for crisis composure as a learnable practice.
-
Design Principles Behind the ktsu.dev .NET Library Catalog
ktsu.dev is the open-source .NET project I have been building since 2023. The libraries vary in size and scope, but they share four principles that govern what I will build and what I won't. This is the design philosophy underneath the library catalog.
-
Atomic Configuration Persistence for .NET Desktop Applications
Persisting application config is the kind of work every project does, every project gets slightly wrong, and every project regrets having rolled its own. ktsu.AppDataStorage is the small library I built so I would never write that code again (safe writes, debounced saves, automatic backups, and a one-line API).
-
Type-Safe Strings, Paths, and Physical Quantities in .NET
Primitive obsession is the bug class where everything is a string or a double and the compiler can't help. ktsu.Semantics is the library I built to make domain types impossible to misuse. Email addresses, file paths, temperatures, and user IDs become first-class types with built-in validation and zero runtime cost over their primitives.
-
Build Infrastructure for 79 .NET Repositories with a Custom MSBuild SDK
How a custom MSBuild SDK, topological dependency sorting, automated versioning, and cross-repo synchronization keep 79+ independent .NET libraries building and publishing consistently.
-
A Layered Security Architecture for .NET Data Processing
A layered security architecture for .NET data processing systems: input validation, JWT auth, role-based access, rate limiting, encryption, and security headers with working code.
-
Distributed Tracing and Structured Logging in .NET
A scientific approach to debugging .NET systems: correlation IDs for request tracing, structured logging, Activity-based distributed tracing, and practical patterns for finding bugs faster.
-
Reducing GC Pressure in .NET with ArrayPool and Span
Practical techniques for reducing GC pressure in .NET: object pooling, Span<T>, ArrayPool, LOH management, and continuous memory monitoring.
-
Threading and Synchronization Patterns in Production .NET Libraries
Practical threading patterns from production .NET code, including lock objects as parameters, thread dispatch queues, ReaderWriterLockSlim vs Lock, and how to actually test for thread safety.
-
Eliminating Primitive Obsession in C# with Semantic Types
How wrapping strings and paths in semantic types catches entire categories of bugs at compile time, and a look at the CRTP-based approach used in the ktsu.dev Semantics library.
-
Enforcing Using Directive Grouping with IDE0055 in C#
How C#'s IDE0055 formatting rule behaves with using directives, why some formatting problems never trigger a violation, and how to work around it.
-
Object Initialization Order in C# and How to Design Around It
How C# object initialization order works, why object initializers run after the default constructor, and how to design types around that behavior.
-
Duplicate Project GUIDs and MSBuild Build Server State
Debugging a cryptic .NET restore error that turns out to expose a design flaw in MSBuild's GUID-based project identity system, and why it bites anyone using Git worktrees or forks.
Nothing matches that combination.