Brackenmere / dev log

Posts Building Brackenmere

Performance by construction

A week in, Brackenmere ran well on my Mac and I had no idea what it cost anyone else. One day of measuring turned performance from a clean-up job into rules the tools enforce, and an overnight blind review stopped the agents optimising away detail my kids would see.

G10 6 min read

How shall I tell it?

A week in, Brackenmere ran nicely on my Mac. That was the trouble: I had no idea what it cost anyone else, and every new world, kit piece and Meshy model was adding to a bill nobody was reading.

Two days earlier the asset diet had shrunk what already existed. On 26 September a cloud session was working through a stack of layers for me (agent-ready docs, gameplay systems, a pile of new kit pieces), and partway through I asked for something different: "Is it worth adding in here a performance review so we're not creating items... incorrectly that we'll need to mass change later on." I wanted the whole game looked at, "run time / mb load in game", and I wanted "anything new we produce" to stay "performant as well".

First person in the village square, facing Gorak the orc with his sword and shield, a hunched creature beside him and the forge glowing behind Gorak at fighting distance on High, with his colour maps back at full size. More on why below.

Measure everything first

I tacked a format question on the end: "Also is the use of avifs an option over webp?" The answer was more useful than a yes. AVIF only makes the download smaller; once decoded, a texture takes the same graphics memory whatever it arrived as, and graphics memory was the real problem. The better tool for 3D textures was KTX2, which the graphics card reads while still compressed. "Ktx2 sounds great, yep add that in and queue up layer 4," I said, and it became the fourth layer.

It ran in that cloud session, a Claude owner on a machine with no graphics card, mostly while I was away.

It began by rewriting the measuring tool. One command now builds the real game, opens every world at every quality tier and records downloads, GPU texture memory, draw calls, shaders compiled after the world said it was ready, and the first frames you would see.

Three numbers stood out.

Under the hood The village needed about 1.5 GB of GPU texture memory: every model texture was a decoded image, so a 2048 pixel map costs 21 MB on the GPU however small its file. Every world compiled all of its shaders (19 to 69 of them) on the first frames after it opened. And every world downloaded every other world's code: 527 KB of gzipped JavaScript wherever you went.

The fixes

Textures that stay compressed on the GPU. Models and 3D surfaces moved to KTX2, a format the graphics card reads without unpacking. Painted pictures (signs, portraits, rugs) got an AVIF beside their WebP. Where a cheaper encoding showed close up, those surfaces kept their old maps.

The village square from above, before and after the texture change, looking the same The village square before the round (left) and after (right). The right one needs about a fifth of the GPU memory.

Shaders compiled while loading. This was the hidden one. On a real graphics card it is a hitch the moment you arrive. Now the runtime compiles every material in the scene, hidden ones included, and draws one frame behind the loading notice before revealing the world.

The game's loading notice, "Loading Brackenmere...", over an empty scene with the HUD around it Where the stall went: behind the loading notice, not on your first steps.

Each world its own download. A world now names the kit categories it places from, and only those ship with it.

Nothing was free: downloads went up about a tenth, and loading took longer, because the work from your first frames now happens before them. I was happy with that trade.

Under the hood Across all eleven worlds, GPU texture memory fell from 4,187 MB to 1,094 MB; the village from 1,514 MB to 279 MB. Shaders compiled after ready went to zero. JavaScript to open a world fell from 527 KB to between 373 and 484 KB.

Guardrails, not good intentions

The fixes mattered less than what came with them. Two days before, I had ruled that performance numbers are reported, never asserted. The plan I said yes to with that KTX2 reply drew the line properly: keep my no-numbers rule for how things look, but let limits that depend only on the assets fail the tests, while numbers that depend on the machine are reported and never fail.

So every kit piece has a budget for its category or must say why not. Every shipped file sits inside a size class that the import script also enforces, so an oversized model is refused before it lands. Each world's JavaScript has a ceiling, and a committed baseline per world warns, without failing, when something grows by more than a fifth.

It became golden rule 12: performant by construction. The cheap path is the one the tools take.

Under the hood The first overrides were honest: Meshy's crates and treasure chest came in at 40,000 triangles, as heavy as a building for a pile of boxes. They stayed, with a reason on file to re-simplify them.

Four decisions and one condition

When I checked in that evening ("How's it going? What else is to go") the last fixes were landing. The audit left four calls for me: load the training square's monsters only when you walk towards them, drop character colour maps from 2048 to 1024 pixels, draw the hedge maze's full-detail hedges over a shorter distance, and stop bottles and candles casting into the hearth's shadows. Just after midnight my answer was: "Yes to all your questions if you think it's a good idea for performance and doesn't degrade from the experience."

The agents took that condition literally. Overnight they applied each decision on its own commit, measured it, and handed before and after shots to an independent art reviewer who did not know which was which.

It saw nothing from three of the decisions. From the textures it saw loss in first person on four characters, and called two more borderline. My condition said no, so all six went back to full size, and the saving shrank from 9.4 MB to 5.7. The maze had been tried at 18 metres first; the corners of the widest view went flat, so it settled at 22.

Gorak's tunic hem up close at 1024 pixels (left) and 2048 (right) Gorak's embroidered hem at 1024 pixels (left) and 2048 (right). The reviewer caught it blind; it went back.

The next afternoon

I picked the rest up from my own laptop: "Let's install the KTX, then work through the decisions. Ask me upfront so I can leave you work." Among the big jobs I ticked the performance leftovers, and on castle torches I wrote: "Shadow torch light can be for more capable machines with a higher graphic setting." Torch shadows went in on High only, and once their cost was measured I kept them.

The budgets did their job too, which was to make me decide, not to block. When the town's save outgrew its limit: "Let's raise the bar to 150Kb, and accept the 96Kb so we stop running into these issues." When Kit Yard needed room for the castle walls: "Raise to 200Kb, accept." And when an agent dropped some of my new props to 512 pixel textures, I sent them back to 1024, for the reason the blind review had already shown: the loss is visible.

What I took from it

  • Measure the whole game, then fix. The shader stall was invisible until something counted it.
  • Put the rules in the tools.
  • Fail on what is the same everywhere, report the rest.
  • Let a blind reviewer guard the look. Optimisation always looks fine to the one doing it.

A week in, Brackenmere ran nicely on my Mac. That was the problem: I had no idea how it ran on anyone else's computer, and every new world, building and model was making it heavier. So I spent a day having the AI helpers measure everything, then fix what they found, and then set rules so it stays light.

First person in the village square, facing Gorak the orc with his sword and shield, a hunched creature beside him and the forge glowing behind Gorak up close, with all the detail in his clothes kept. More on why below.

What they found

Three things stood out:

  • The village was using a huge amount of the computer's graphics memory, because every picture on every model was stored in its biggest, fully unpacked form.
  • Each world did a burst of setting-up work in the first moments after it appeared. On a normal computer, that would feel like a stutter the moment you arrive.
  • Wherever you went, the game downloaded every other world too.

The fixes

The pictures on models now use a format the graphics card can read while they are still packed small. It looks the same, and needs about a fifth of the memory.

The village square from above, before and after the texture change, looking the same The village before (left) and after (right). They look the same, but the right one is far lighter on your computer.

The setting-up work now happens while the loading message is on screen, so your first steps are smooth. And each world only downloads what it needs.

The game's loading notice, "Loading Brackenmere...", over an empty scene with the HUD around it The stutter moved here, behind the loading message, instead of on your first steps.

Loading takes a little longer now, because the work happens before you start playing instead of during. I was happy with that trade.

Rules, not good intentions

The fixes mattered less than what came with them. Every new piece of the game now has a size limit, and anything too big is turned away before it gets in, unless there is a written reason. Think of a builder's checklist that will not let a delivery onto site if it is the wrong size. Staying light is now the easy path, not a clean-up job later.

Don't lose what my kids would see

The helpers suggested a few savings that needed my say. I said yes to all of them, on one condition: only if it does not make the game worse to play.

They took that seriously. For each change, they showed before and after pictures to a separate checker who did not know which was which. Most changes made no visible difference. But making the characters' clothing pictures smaller did show in first person, so those went back to full detail.

Gorak's tunic hem up close at 1024 pixels (left) and 2048 (right) Gorak's embroidered hem, smaller (left) and full detail (right). The checker spotted the difference without being told, so it went back.

The castle torches also cast proper shadows now, but only on the highest picture setting, for more powerful computers.

What I took from it

Measure the whole game before fixing anything: the stutter was invisible until something counted it. Put the rules where they are checked automatically. And let someone else judge the look, because a saving always looks fine to the one who made it.

Building Brackenmere

G22 6 min read

Twenty-seven pieces of feel

On 8 October I asked the agents what makes a game feel real, as someone who is not a game designer. One research pass became 27 small pieces of atmosphere and polish, built and merged in a day, and a lesson about how many agents one Mac can carry.

Building Brackenmere

G06 5 min read

The asset diet and quality tiers

Five days in, Brackenmere's village was a 215 MB download and the castle took a thousand draw calls. In one day in the cloud the agents put every model on a diet, measured before fixing anything, and gave the game Low, Medium and High graphics, without changing how it looked on my Mac.

Building Brackenmere

G37 6 min read

Four rooms, built from the pictures I sent

I sent the agents an isometric picture of the inn I wanted, then three more for my home, the Forge and the Apothecary. All four were rebuilt in one evening. It took a second day, and my complaint that the inn felt stale, to make them warm.