On architectural ownership, testing as evidence, and what happens when a software project is optimized for an AI coding agent.
There’s a train in Anna & Cora’s World that runs between five stations. It travels at seven meters per second, stops for fifteen seconds at each station, and follows a schedule calculated from the game’s world clock. When my girls play together on their iPads, both devices independently work out where the train should be. We don’t continually send the train’s position over the network. Both devices know the time, and both know the schedule.
As a software engineer who’s never built a full-featured game, I think that solution is pretty slick. Distributed systems are familiar territory for me. Unity game development, as I’ve discovered, is something else entirely.
In my previous article, I wrote about diving into AI-assisted game development, inspired in large part by watching my daughters play. I wanted to build a few experiences they could enjoy without ads, in-app purchases, or a never-ending stream of incentives to keep playing. Anna & Cora’s World became an open-ended place for them to explore together. There are homes to decorate, neighbors to visit, a zoo, a water park, shops, a railway, and small adventures that actually have endings, even while the world itself continues.
Development began with OpenAI Codex, though I moved most of the work to Claude Code by the second day. Claude Opus 5.5 handled the bulk of the implementation through Claude Code. For character dialogue and refinements to the generated 3D models, I returned to Codex, running GPT-6 Astra. I’d had better results with Astra on those kinds of tasks in earlier experiments, so I decided to divide the work accordingly. A repository snapshot taken less than two weeks into development showed 214 commits, roughly 58,000 lines of runtime C#, 29,000 lines of Blender Python, and 536 generated 3D models. The world covered 460 by 320 meters across ten districts. Of those 214 commits, 184 were co-authored by Claude. The implementation has been overwhelmingly AI-assisted, and I don’t want the numbers to suggest otherwise.
Before these projects, I had never built a game in Unity or tried modeling anything in Blender. I know a fair amount about software engineering, but I have no practical frame of reference for what good work in either tool looks like. I can reason about state ownership, networking, test isolation, and coupling. I cannot judge a procedural character rig against Unity’s established animation workflows or tell you whether a working 3D artist would ever choose the modeling pipeline we’ve ended up with.
I knew that going in. Rather than spend months becoming proficient in both tools before making something playable, I decided to rely on the agents for much of the domain-specific implementation and use the engineering skills I already had to set boundaries and demand evidence. That decision made the game possible. It also left me responsible for implementation choices I can’t independently assess in the way I normally would.
Designing the workflow around the agent
The first major choice was to generate nearly everything from code. Blender runs headlessly and executes Python scripts that build models from geometric primitives and curves. An export manifest coordinates 28 scripts and verifies the expected results. Each model is exported as an FBX file with accompanying JSON describing its colors, which Unity later applies using its own materials.
Unity then uses C# editor scripts, led by GameBuilder.MakeScene(), to assemble prefabs, materials, scenery, characters, cameras, and UI into a single scene. The positions come from C# layout definitions that are also consulted at runtime. The generated scene and prefabs aren’t kept in Git; they’re rebuilt from committed source code and exported assets. Most builds don’t need to run Blender because the FBX exports are already committed.
01 / Code-generated world
Source code and exported models are committed; Unity scenes and prefabs are build outputs.
Blender Python, 28 model export scripts, produces FBX models and color JSON (536 models, committed to Git). Unity C# builders, through GameBuilder.MakeScene(), regenerate the Unity scene, prefabs and materials, which become the playable builds: Mac for tests, iPads for play. C# district layouts supply shared positions to the Unity builders and to the runtime world code, which uses the same positions: editor and runtime agreement.
Generation is reproducible, but direct manual edits to the built scene do not survive regeneration.
I can see why this arrangement emerged. A coding agent is comfortable editing Python and C#, invoking build scripts, inspecting output, and repeating the cycle. It is much less natural for that agent to spend its day visually editing a Unity scene or moving objects around in Blender. Turning visual development into a repeatable source-code pipeline plays to the agent’s strengths. It also plays to mine. Reproducible builds and a single source of truth for coordinates are ideas I understand and can evaluate.
An unexpected benefit was how easily I could move work between models. Opus was particularly good at writing handoff notes for Codex, documenting what had changed, which architectural conventions needed to be preserved, and what remained to be done. Codex could pick up those notes, inspect the same source files, and use the existing build and test infrastructure for its own polishing passes. I could divide work according to the strengths I’d observed in each model without having to reestablish the project’s context every time. The repository and tests provided a common reference, while Opus’s handoff notes helped explain the decisions behind the implementation.
The trade-off is what happens when someone wants to work in the editor. A Unity developer might expect to select a building and move it a few meters. Here, that adjustment belongs in a layout definition, and a manual change to the generated scene may be overwritten the next time we rebuild. A new hairstyle may require changing the Python that constructs it, regenerating the FBX, and checking the corresponding Unity assembly rules. We’ve moved the work from manipulating assets to maintaining the code that produces them.
The seam between Blender and Unity is especially revealing. Certain names are contracts: __W14 through __W19 distinguish hair variants, Head_FitBow__W14 locates an accessory, and parts containing Glow are treated differently at dusk. These aren’t merely descriptive labels. They carry information that another stage of the pipeline interprets. Rename something on the Blender side and the C# compiler won’t know an attachment has broken. We now have explicit tests for some of those conventions, contact-sheet renders of models, a manifest to detect missing exports, and a check for coplanar faces that might flicker.
A recent architectural review also caught a problem familiar from other generated-code workflows: our generated Unity scenes, prefabs, and materials were introducing identifier churn into Git. We stopped committing those artifacts and made their regeneration part of the build process, while retaining the exported Blender models and their material definitions in version control.
That cleaned up the repository, but it also reinforced a point I keep coming back to. We’ve effectively built an internal asset-generation platform, with contracts and maintenance obligations of its own. The workflow is well suited to an agent that can modify source files, regenerate assets, and validate the results automatically.
The part of the design I can actually defend
The strongest architectural boundary, from my perspective, is between the game rules and Unity itself. The World/ classes handle decorating, coins, friendships, stickers, adventures, and other rules without depending on GameObject, MonoBehaviour, or editor lifecycle behavior. They still use some Unity value types, such as Vector3 and Color, but the logic can be compiled and exercised without launching the engine.
The central class, WorldSimulation, owns the mutable world state and handles 61 varieties of action request. Views request changes rather than mutating the saved world directly. The networking host, SessionHost, checks the validity of many incoming actions before forwarding them into the simulation. This is a familiar application of separation of concerns. Game rules stay independent of rendering and other engine-specific behavior, with a single authoritative simulation responsible for state changes.
That separation became particularly useful when multiplayer entered the picture. I wanted the girls to play together on their iPads over our home Wi-Fi, without player accounts, a cloud backend, or a recurring infrastructure bill. One iPad hosts the game, while others discover it through Bonjour, request permission to join, and communicate over framed TCP messages. The host maintains the authoritative state of the world, validates player actions, and distributes updates to connected devices. This prevents two devices from independently deciding whether the same coins have been spent or an adventure reward has already been claimed. After all that engineering, I’d essentially reinvented the LAN party, only this time with two seven-year-olds and a couple of iPads.
02 / Host-authoritative multiplayer
One iPad owns the mutable world; visitors ask for changes and render approved state.
A visitor iPad sends player requests over TCP to the SessionHost, which handles admission and validation and passes actions to the WorldSimulation, which holds the rules and mutable state. Its changed world state, a new revision, becomes host-approved snapshots and movement updates, which are sent to the visitor views to draw. A synchronized clock lets each device compute train, neighbor and ride motion locally.
Bonjour discovers sessions. The host is a deliberate single point of failure; there is no host migration.
The protocol is already at version 27. It uses an explicit length-prefixed format around JSON messages, caps message sizes and queues, handles approval for visitors, and attempts to reconnect after interruptions. World snapshots are chunked when necessary; ordinary saves are only about 11–15 KB. When only movement has changed, the game can send much smaller updates instead of another complete snapshot. The host has provisions for saving and briefly suspending sessions when iOS interrupts it. All of that is more infrastructure than I imagined when I first said, “The girls should be able to play together.”
The boundary makes sense to me. We don’t need a global matchmaking system, internet-scale concurrency, or server-side persistence. A host-authoritative model is simpler than operating an online service, even though it creates a deliberate single point of failure: if the host leaves, another iPad doesn’t seamlessly take over. Host migration would be an interesting technical project, but I don’t see it earning its way into this particular game.
The clock presents a similar trade-off. An eight-minute world day drives lighting, trains, neighbors, zoo animals, and several attractions. Rather than constantly exchanging position updates, each device calculates these predictable activities independently using a shared clock and the same underlying rules. For something following a fixed schedule, this is both elegant and efficient. Things get considerably more complicated when behavior becomes dynamic, responds to individual player actions, or introduces randomness that isn’t synchronized across devices. The approach works because much of the world follows predictable routines rather than maintaining independently evolving state on each device. That may limit how spontaneous the world can become, but for a pair of second graders, a little predictability isn’t necessarily a bad thing. Sometimes knowing the train will show up when you expect it to is part of the experience.
There are risks I can already see. The wire format contains some compact, delimiter-separated fields instead of typed messages, and the protocol has evolved rapidly. Local-network restrictions and host approval are reasonable controls for a family application, but I wouldn’t mistake them for a security design intended for arbitrary networks or unknown players. The responsibilities of SessionHost and WorldSimulation are also growing: at the latest review they were 1,554 and 1,526 lines respectively, with the host having been just 895 lines days earlier. Newer features have begun moving their rules into smaller dedicated classes, which is a direction I want to preserve.
Code review is not the safety net I wish it were
Code review still helps me identify familiar problems with coupling, state management, and unclear responsibilities. It’s much less useful when I’m looking at a procedural character rig or a Blender mesh generator. I can follow the implementation, but I don’t have enough practical experience with either tool to recognize whether the approach itself is sensible. Familiar syntax can be a dangerous substitute for knowing the technology.
I understood that problem early, so I made testing central to the workflow. At the latest count, 757 portable C# tests passed in roughly one second. The Unity test run recorded 943 passing tests and takes a minute or two. The full Mac playthrough uses four game instances in parallel to walk through different parts of the world, perform interactions, and capture about 200 screenshots over roughly six minutes. Other scripts exercise individual areas and multiplayer scenarios. A two-iPad test runs the game on the actual target devices and brings back performance logs.
03 / Evidence, not just passing code
Different checks answer different questions; none independently proves maintainability.
Five layers of evidence in order of increasing realism. Portable C# rule tests, 757 in about a second, ask whether the rules behave as specified. Unity tests, 943 passing in one to two minutes, ask whether the scene is coherent. Scripted Mac play, about 200 screenshots in about six minutes, asks whether the script finishes. Two actual iPads, testing network and performance in about eight minutes, ask whether the devices agree. Anna and Cora's open-ended play asks what feels wrong, and whether it's fun.
Counts and timings reflect reported runs in the October 18 field notes, not independent test coverage.
The agent can share mistaken assumptions with its tests; device evidence and human observation reduce that risk.
That order matters. A one-second rule test can check that furniture doesn’t block a door, but it can’t tell me whether the chair appears where it should. A Unity test can catch scene geometry issues, but it won’t necessarily reveal that an interaction is confusing. A Mac walkthrough verifies a path through the actual application, but can’t establish that two physical iPads will behave the same when one is interrupted. None of those mechanisms tells me whether the game is enjoyable.
The testing setup has already exposed flawed assumptions. Early scripted multiplayer players coordinated their progress through files on the development machine. That worked until the first two-iPad run, where the approach broke. The test was changed so each scripted player signals its progress through the game’s own networking mechanism. That was a meaningful improvement because the test now exercises the communication path we’re claiming works.
There’s also a limit to how reassured I should feel when the agent writes both the code and the tests. If Claude misunderstands a requirement and writes a test for that same misunderstanding, both may agree perfectly. The generated scene and runtime also share layout definitions, which is useful for consistency but means a bad coordinate can be consistently wrong in both places. The tests can establish that the two implementations agree without independently proving that the result is correct.
This is an old testing problem in a new workflow. Where does the test oracle come from? Our answers include written feature expectations, screenshot inspection, the operating system and hardware actually running the game, and direct playtesting by the intended players. Over two days of playing the game, the girls produced a 21-item wish list, which became four batches of work. They find missing interactions and awkward experiences that aren’t in the scripts because they use the world rather than execute a specification against it.
The custom systems I can’t confidently defend
Animation and audio are where the gap in my experience becomes particularly obvious. The project doesn’t use conventional Unity animation clips or Animator controllers. CharacterRig, now roughly a thousand lines, poses joints through code every frame. Walking phase follows distance traveled, which avoids sliding feet; hair and clothing use spring motion; dances and shared interactions use timing and deterministic identifiers to produce consistent animations on multiple devices. Characters and many of their clothing variations are themselves generated through the modeling scripts.
The audio engine is just as bespoke. There are no imported audio files. It generates 72 named sounds through synthesis, while a streaming music system uses 48 voices across 17 layers. Different districts choose different instrumentation, and some effects are rendered to fit the current chord. I have to admit, though, I enjoy the engineering behind both systems.
I never did the comparison I’d ordinarily expect an engineer to make before introducing custom animation and audio subsystems. We built them, they work in the scenarios we’ve tested, and I’m left wondering whether we needed either one. They may suit a workflow in which an agent generates everything through code, but they could also be elaborate substitutes for tools I simply haven’t learned how to use.
This is where my earlier experience building an absurdly complicated tip calculator comes back to mind. The model can recognize technical complexity and still be much more eager to discuss how to build something than whether it belongs in the project. With an agent capable of producing substantial implementations quickly, the initial engineering effort no longer provides much resistance. I can have a custom animation framework before I’ve decided whether I need one.
The price of owning that code arrives later. If the character rig breaks, I may be capable of reading the implementation and still be incapable of repairing it confidently without the agent. That is a dependency, even if the code is local, documented, and under version control.
The numbers I trust, and the numbers I don’t yet have
The same difference between implementation and evidence shows up in performance. In an earlier performance test, an older ninth-generation iPad hosting the game averaged 59.2 frames per second against a 60-frame target. A newer A16 iPad visiting the session averaged roughly 59.7 to 59.8. Those numbers are encouraging, especially because an older iPad is part of the intended hardware mix.
But they were measured before six more districts were added. The latest architectural review notes static mesh combining, GPU instancing for scattered greenery, distance-based hiding of districts, and a simplified toon-rendering pipeline. It also notes that there isn’t yet a conventional level-of-detail system and that a Mac playthrough saw roughly 1,600 draw calls in the village green. None of that tells me whether the expanded game still delivers the same frame rate on physical devices. I also don’t have enough evidence about sustained play, thermal behavior, battery use, or frame-time spikes.
The solution isn’t to ask Claude to add another optimization merely because one metric looks suspicious. It’s to rerun the device tests on the current build, inspect the actual bottleneck if one appears, and then decide what to change. That’s a fairly ordinary engineering discipline, but it matters more when generating another subsystem is so cheap.
The architecture has grown in days, while the measurements supporting some of its assumptions have aged across those same days. A green benchmark from last week can become misleading surprisingly quickly when the game has expanded substantially since then.
Who is this architecture actually maintainable for?
Suppose I handed this repository to an experienced Unity developer. The source of truth is textual, and generated Unity state isn’t meant for hand editing. An agent can change a geometry script, run a build, examine a contact sheet, execute a scripted walk, and repeat. The same workflow would require a human developer to learn a collection of generators and conventions before making changes that might otherwise happen in the editor.
If Claude Code disappeared tomorrow, I would have uneven prospects of maintaining the application myself. I could reason about networking and game state, probably diagnose many ordinary C# problems, and lean on the specifications to understand intended behavior. Fixing a difficult procedural rig or redesigning a Blender export script would put me back into tools I don’t know. Reproducible builds and passing tests would make that learning easier; they wouldn’t do it for me.
I’d like an experienced Unity and Blender developer to examine this project without first being told why we chose the current approach. Which parts would they appreciate? Where would they say I’d reinvented an existing tool badly? What would make them reluctant to inherit the code? Their review would provide something my automated tests can’t: experience with the development practices and alternatives I skipped while building this game.
There’s a difference between an agent making an architectural decision and me accepting responsibility for it. I can defend avoiding a cloud backend and explain why separating rules from engine code matters. I can’t make the same claim about the custom animation and audio pipelines. Ownership means recognizing where my judgment is informed, where I’m relying on observed behavior, and what happens if that behavior stops matching the tests.
The consequences of getting it wrong
This is a children’s game running on our home network. The consequences of a bad architectural bet are mostly measured in time spent fixing it, lost momentum, and two moderately disappointed seven-year-olds (which, I can assure you, is plenty of motivation not to screw it up). I’m comfortable accepting a degree of architectural uncertainty here that I wouldn’t tolerate in software that moves money or carries more serious obligations. The speed at which an agent can produce an implementation doesn’t change the need to evaluate the risks of getting it wrong.
And the context here remains wonderfully simple. Anna and Cora want to explore, decorate their houses, visit their friends, and catch a train. They couldn’t care less whether the train follows a deterministic schedule, whether the asset pipeline is reproducible, or whether an experienced Unity developer would have made different decisions. They just want the train to arrive.
So far, it does. I’m still learning what it will take to keep it that way. And I can’t help but laugh at the fact that, of all the things building games for my daughters has taught me, I’ve somehow developed opinions about distributed train synchronization.
Architecture and repository figures reflect the October 15 review at commit bcd39aa, with testing notes updated October 18, 2026. The last referenced physical-device performance run occurred October 9, before six additional districts were added. Diagrams are conceptual summaries rather than literal class diagrams; explanations of why choices made sense are retrospective assessments, not claims that each rationale was recorded when the code was written.