A Big Win, and a Change of Pace

Real life has intervened, so I’m slowing down my pace a bit. There’s a combination of factors at play, but all of them involve chaos in my non-programming life.

I have, however, still been making progress on both the game itself and the process. And that progress has already resulted in a PR that genuinely surprised me.

Apparently I’m doing a series of blog posts about this project!

Part 1: Six Days Equals Six Weeks

Part 2: Specialists in the Factory

Part 3: The Invisible Work Matters

Part 4: Progress, 37 Days in

Part 5: The Meta-Game Begins

Part 6: In Which I Intervene in the Code

Part 7: Quality Requires Visibility

Part 8: Roost, Terrain, and Sound

Process: planning got expensive on purpose

The biggest driving factor behind all of the process changes is that the LLMs have gotten pretty aggressive about finding (and doing) busy-work, while the quality of results has been mixed. The end result is a lot of burned tokens for PRs that require a lot of human attention, even if I’m not looking at the code. Now, I’m going to burn more tokens, but hopefully I’ll spend less of my time correcting the agent late in the process.

I’ve made a bunch of adjustments to the process:

  • I’ve added the “superpowers” skill to provide a bit more structure and diligence within the context of an individual agent’s operation.
  • I’ve informed the agent designs based on Humanlayer’s “Why Software Factories Fail” article.
  • New agent roles: intake-reviewer, planner, plan-reviewer, test-auditor. The plan now also gets an adversarial review pass of its own.
  • “Slice wins over tier.” Work is now carved up so each piece is independently touchable end-to-end, and the model tier is whatever that piece needs. The old rule cut chunks by cost, which produced layer-shaped work and resulted in some painfully stupid bugs landing on main.
  • Planners now produce an HTML mockup plus a cited before-state.

I’m moving away from roost, and I’m adopting several tools from my friend Jeremy’s stack.

There’s nothing wrong with roost; it’s just solving different problems than the ones I want to lean into. roost is mostly about reducing needless chatter between agents by controlling how they interact with one another. It is also highly tied to the claude harness. Right now, I’m less worried about inter-agent chatter than I am with being able to use the best model (including ones that don’t fit into the claude harness) for a given sub-task. Later in the article, you’ll see where I used Antigravity, Codex, GLM-5.2, and Claude for a task.

The tools that have been in place for a few weeks now:

Tools I’ve just recently added:

  • constrain: The first step of moving towards Jeremy’s notion of “induced understanding”, to ensure that issues are adequately fleshed out before any work begins.
  • simulacrum: An agent that acts like Jeremy himself. It essentially uses the Socratic method to stress-test a plan.
  • An adaptation of advocate into my claudebox harness.
    • The deviation from his tool is strictly because advocate only works with API-usage when used with Anthropic models and I remain cheap.
  • Some specific agent skills that represent the core context engineering from some of the components I’m not ready to fully adopt yet.

More of his tools will be making it into the mix as time goes by, but some of them need some work to be viable here. Notably, pact needs to be updated to support Ruby and C#. I see transmogrifier being on the very-near-term horizon.

I want to get to his concept of “signed authority”, because I’ve already seen cases of bots bouncing off of each other and throwing themselves off the plan with no human gate. And, in a different project, I’ve already seen the PR reviewer and responder fall into an infinite loop. The remaining tools from Jeremy’s stack are a touch more complex to spin up, so they’re going to be coming a bit later.

An important thing I haven’t done yet is fully disintertwingle Superpowers, the application of learnings from the Humanlayer article, the vestiges of Roost that exist in the agent definitions, and Jeremy’s stack. That’s probably going to happen as I incrementally transition in more of Jeremy’s stack and move to more structured orchestration of the factory. It’s possible that they are working together additively, or it’s possible I’m getting worse results than I otherwise could because they’re interacting poorly.

That said, there is a small bit of preliminary evidence suggesting that at least some of the work harmonizes well: The plan-reviewer sub-agent (one of four agent definitions that came in when I adapted learnings from the Humanlayer article) was (automatically) used during the “induced understanding” phase, and caught a defect in the plan that would have led to the save/load mechanism not working in the real scene, even if the tests passed.

The Big Win

To test out all of the changes, I threw a task I’ve been kinda dreading at the factory: I have an audio test scene, that lets the audio engineer fiddle with the settings for the music and various sound effects. The audio engineer, understanding how complex the soundscape of the game is, asked for a way to be able to test things in context of actual play.

My first idea for this was to have a set of predefined tower groups, comprising a progression through the game. A slider would enable or disable them progressively. Add to that a set of controls to spawn a particular number of orcs at once, and you’ve got something solid. This ran into a couple problems:

  1. Manually setting up that progression was pretty tedious work.
  2. Anything I come up with presupposes a specific pattern of play that may or may not give the audio engineer the information he needs to get everything right.

My second idea was… presets. As part of the process of wiring in enough of the game as was needed for towers, and orcs to do their thing and spells to be castable, it was already the case that the scene is set up so the audio engineer can place towers wherever he needs. So why not just let him set things up the way he wants (it’s way less tedious to use the in-game placement than the in-editor placement), and save a config as a preset?

This would have the side benefit of giving me an opportunity to exercise the as-yet-unused save/load system that the factory has been building and maintaining in the background!

The first idea had already been implemented. It took a fair bit of back-and-forth with the bot to get it right, but I had set up a tower group and could flip it on and off as I saw fit. Orcs spawn onto free spaces in the desired quantity as needed.

So the second pass needed to rip out most, but not all, of the old mechanism and replace it with the new one. It needed to adjust any assumptions in the save/load system that might present a problem: We don’t want it saving orcs, and we need to have multiple saves associated with this particular scene (a capability my game design does not require).

So, I went through the process. I threw the factory at my poorly-specified Github issue. It reads, in its entirety:

Replace tower grouping mechanism in audio test scene with prefab slots and save/load mechanism (no pre-defined values; let the audio engineer set those up!). This should be durable across playerpref wipes, so save it in a file in the application data path.

The first step of the process is “Induced Understanding.” The bot chewed on it for a bit and started coming back with questions. Did I mean that the numbered presets work in a similar tiered-setup manner as the existing mechanism, or as completely standalone configs? (The latter.) Do we need to care about the audio engineer running into gold limits? (No, I’m granting him a million gold to start.) Do the presets need to allow for labels? (No.) How many slots? (Let’s go with 6 for now.)

From there it went to the intake and planning steps. This is where simulacrum, claudebox (advocate, in a perfect world), and constrain come into play.

Use simulacrum, claudebox, and constrain to vet the plan.

As it noodled on things, I gave it a couple nudges.

I should note that this is a fairly simple use-case, and we don’t need extensive guards around corner-cases. Notably, the scene is guaranteed to begin with no towers placed / orcs in play, and that will not change.

Along the way, I answered other questions that came up.

The lack of a make target is not reflective of an absence of ability to do a mac build – it just means I’m doing those by hand from the editor. That may be worth recording in kindex. You’re right that there’s no build profile for a native mac build of the audio test scene, but that’s a trivial change to be made if and when it’s needed.

LOAD doesn’t need a confirm gate for the time being.

After a few rounds of back-and-forth iterating on the plan, it had something it was happy with and asked for sign-off. I looked it over, decided I was happy with it, gave my approval, and got the next phase of the process going.

Right now the factory isn’t orchestrated in an automated way, so there’s explicit prompting required to shape the work. Turning this into a structured flow without human orchestration of the graph is in the works.

Note the use of Antigravity for orchestration, GLM-5.2 for implementation, Codex for writing tests, and Claude as the validator holding it all together.

Approval recorded on PR, and PR merged. You are validator and will use the /validate skill, you will use tmux to fire up an agy agent as the /orchestrate skill worker, a codex instance to use the /test skill, and a GLM-5.2 instance with Ollama to use /engineer skill.  You’ll coordinate with each and set up a poll to ensure you remain engaged. Follow the standards defined in ~/mrjoy/factory and the guidance in ~/mrjoy/tools about how to build solid production software.  Share the instructions, skills, and goals with the other agents according to the standards defined in the factory and manage the process to deliver solid, tested, validated code.

The separation of implementation from test authoring serves two purposes:

  1. An agent whose primary work product is tests is more focused on test quality than an agent that has to write tests as a checkbox item in service of its main goal of writing code.
  2. It creates an opportunity to enforce having the implementer not see the tests, preventing gaming. (Note that my factory won’t get this benefit until proper orchestration is in place. And, I think pact is the relevant component for that process.)

After this, the only question that was raised during the implementation step was whether the save button confirmation label should be “SAVE 3?”, or “3?”.

The Result

Here’s the part that makes me happy: I ran through the short, focused, surprisingly thorough validation checklist it gave me and… it worked shockingly well. All the desired functionality? Implemented exactly as I expected. The corner-cases it thought of? Flawless. The audio test scene wasn’t quite shippable as-is, but the issues I found were likely all in pre-existing components rather than the new code.

Copilot reviewed the PR and found… basically nothing substantive: One highly unlikely corner-case should maybe throw an exception. A couple debug log messages are less informative than they could be.

After some more rigorous usage, I found several defects – all of them pre-existing problems with the save/load system.

I threw in a small scope miss from the previous attempt that remained relevant, and asked it to fix that.

The only hiccup in the process itself? The Tester agent and the Coder agent took a vague directive from the Validator agent and implemented it in different ways. The Validator caught that and course-corrected the agents. It updated the PR with one more validation item for the scope creep I added. The split of Coder and Tester isn’t enforced today. What’s missing is having a loop where Coder’s output is combined with Tester’s output, executed, and just the test results are communicated to Coder. Despite this gap, they still begin their work in separate sessions.

There’s a significant contrast in how things went between the planned part of the work and the scope creep and pre-existing issues I tacked onto the end: Without those, it was one round of PR feedback from bots and the PR was done. With them, it was another few hours of working with the bot to get things resolved.

Game client

If you’re here for details about Hordes of Orcs 3, this is the juicy part: The player-visible changes over the past couple months. Despite my silence (and lack of free time), it’s actually quite substantial!

(If you’re here for the process stuff, note that all of the work described below was done before implementing Jeremy’s stack. The next write-up will have details on my experiences with it in context of actual gameplay.)

Spells

All four spells from the previous games are back. This actually wraps up a lot of invisible infrastructure work that was happening in the background during previous milestones. The spell cursor framework, mana system, targeting behavior changes, and orc AI changes were all landed incrementally before I decided to tackle spells head on. So were the actual logic components for each spell. All this amounts to is wiring everything together and fixing a bunch of small spec misses / defects.

I’m particularly impressed that Opus 4.8 all but one-shotted a dissolve shader! The only thing it missed is that clip only drops fragments when given a negative value. 0 passes. A trivial fix.

The biggest problem encountered was me having a bad idea about how the mana system should work, and having to rip it out and replace it with behavior that matches the original games’ behavior.

Meta-game

Three new upgrades (all-tower damage, arrow-tower damage, weaken-spell range) on a new upgrade-attribute composition model. I intentionally picked overlapping upgrades to stress-test the design of the attribute stacking system. Post-season handling of unclaimed battle pass rewards is in place. Modulo some frustrating rendering bugs related to UXML being a PITA.

UI

This is mostly a lot of little things. Adding scroll bars to scroll views. Making the text field in the feedback window scrollable. Fixing some cases where UI would briefly present with outdated state and then correct itself in a few frames.

The more substantive part is initial work on building out components that use a new UI skin I acquired from the asset store. None of these new UI components are in the game yet. I’ll likely replace the UI panel-by-panel once the relevant components all exist.

Rendering and Performance

There’s now material arbitration for applying device-specific materials, and for handling of conflicting requirements at play-time. (The latter is a consequence of interactions between the teleport spell and unit selection.) This lets me apply simpler terrain shaders on mobile, and handles cases like selecting an orc as it’s in the process of teleporting.

There were also some general optimizations: The terrain shader was a generalized triplanar shader that was performing multiple triplanar texture lookups. For starters, the UV calculation only needed to be happening once. That’s just a straight up fill-rate win. A further optimization is related to the structure of the terrain itself: It’s already broken up into several materials, with the biggest one being the actual ground plane. There’s no reason to do any triplanar computations in this case. So the biggest fill-rate case got a lot cheaper.

There was one facepalm-inducing scope miss: The dirt overlay didn’t receive shadows.

And there were some annoying bugs that got fixed:

  • Dirt overlay culling bounds were wrong, so it would just disappear if you moved the camera in certain ways.
  • The range indicator had a render-order problem against the water.

I’ve also enabled adaptive performance on relevant platforms, and introduced a first-run automatic settings adjustment pass. It’s pretty coarse right now, but I can fine-tune it later as I get more data about devices.

Gameplay

One small change to the game rules came up as I started fiddling with the soundscape: Towers are no longer ready to fire on placement. They have to go through a cooldown first. This is basically so the railgun tower has time for the charging ramp-up sound to play if it’s dropped into the scene with orcs in range already.

I also fixed a couple more cases of orcs getting stranded and shored up the backstop logic in case there are any more.

There are some fixes related to object pooling (a leak from one pool, and a state reset oversight on another).

Oh, and I continue to play with game balance in significant ways.

Backend

There’s also been a fair bit of work on the backend.

  • Security audit: A full adversarial pass using OpenAI’s security plugin (minus the attempt-to-exploit, because the security guardrails blocked it) revealed some DoS vectors, some unbounded request body issues on some routes, and a handful of cheating avenues I already knew about. It also produced a couple findings that are not false positives, but reflect work I’m postponing until we get closer to a production environment – stuff I’m intentionally ignoring for now.
  • Account identity and merge: A potential account merge race got fixed, and a fix related to re-bootstrapping idempotency.
  • Admin surface: The admin UI grew composable filters, a sidebar menu, and some (more) operational analytics.
  • Telemetry: This is the biggest area of improvement on the back-end. There were some missing event types that had been specified but weren’t being emitted. Most events were missing one or more fields. Schema versioning was used inconsistently. All fixed.
  • Data pipeline and metrics: The pre-existing dashboards deviated in subtle ways from what I had asked for, and now their semantics match what I originally intended.

The New Pace

The factory process now front-loads a lot of effort, but necessarily so. We’re past the point where a quick, off-the-top-of-my-head idea can be executed without causing nasty consequences elsewhere in the codebase. That means that I’ll be doing things one-issue-at-a-time – at least until proper orchestration is in place. No more pointing the factory at a stack of issues when I go to bed and waking up to a stack of PRs. Nonetheless, I expect this to involve a lot less rework and thus a more sustainable pace of progress.

I am particularly hopeful that kindex will gradually help diminish the amount of front-loaded work, but I don’t have clear data on that yet. It will depend on building and maintaining a useful corpus. The “useful” part will depend on how well its dream function performs, and on another of Jeremy’s tools – meditate, not yet public.

Unfortunately, my personal life is going to remain chaotic for a while yet, so that isn’t helping with keeping up a rapid pace of development. It’d be nice if the chaos didn’t coincide so well with making major changes, so I could have a clearer idea of the impact of the process changes on their own. Reality isn’t as obliging as one might hope.

Where the numbers stand

The hit to my personal life has left a gaping hole in the numbers. Combine that with the factory developing a propensity to become super nitpicky and aggressively proactive, and a different chart than last time seems worth showing. New issues, rather than all issues opened and closed. The big chunks of red are basically where the factory got proactive. The gaps are where life interfered.

For the sake of making comparison easier, here’s the same chart I showed in the last post, updated:

Comments