<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Trilithon Games devlog</title><description>A small independent studio that builds its games and engines almost entirely with Claude Code.</description><link>https://trilithon.games/</link><language>en-us</language><item><title>Fact-checking the game while writing about it</title><link>https://trilithon.games/devlog/fact-checking-the-game/</link><guid isPermaLink="true">https://trilithon.games/devlog/fact-checking-the-game/</guid><description>The Claude Code session that writes this devlog checks every claim against the game&apos;s records, and it has found errors the game&apos;s own checks missed.</description><pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Wreckwing is built by Claude Code sessions working in the game’s repository. This site and its devlog are run by a different Claude Code session in a different repository. That session writes posts from the game’s own records: its development journal, its changelog, its commit messages and its tracker. Its rules say every claim in a post traces to one of those records and every image shows what its caption says, and before a draft comes to me, a separate agent checks each number in it against the sources.&lt;/p&gt;
&lt;p&gt;I set that up to keep the devlog accurate. It turned out to check the game as well.&lt;/p&gt;
&lt;h2 id=&quot;checking-the-work-four-ways&quot;&gt;Checking the work four ways&lt;/h2&gt;
&lt;p&gt;The game’s work now goes through four kinds of check, and each has caught something the others missed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The game session’s own tests.&lt;/strong&gt; Every feature gets a test that it changes what a player would see, a problem I report closes only when my case is kept as a test, and a smoke test flies a clean build before anything a player reaches is closed. The smoke test flagged enemies spawning inside buildings in two game modes the same night I ran into it in play. &lt;a href=&quot;/devlog/keeping-the-project-out-of-vibe-coding/&quot;&gt;Keeping the project out of vibe coding&lt;/a&gt; describes these rules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reviewers that didn’t build the change.&lt;/strong&gt; A separate model judges visual work from recordings. The first version of the shockwave from a ship’s death stretched the image about eight times in thin bands, and that review caught it. Side agents raise knock-on effects. One pointed out that a new sound for near misses would carry into the next run’s ratings of the enemies, so the sound got a switch to turn it off for a fair comparison.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My play.&lt;/strong&gt; I fly what was built, and my verdicts go back to the sessions on a checklist. A new set of enemies that tested as dangerous as the old ones felt soft in my hands, and the flight records showed why.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The site session.&lt;/strong&gt; It comes to the work after it is done, with no part in building it, and it has to describe what the work does in plain sentences a reader can check.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;catching-a-flash-check-that-was-too-lenient&quot;&gt;Catching a flash check that was too lenient&lt;/h2&gt;
&lt;p&gt;Wreckwing’s blasts flash and warp the screen, and rapid flashing can cause seizures in people with photosensitive epilepsy. The game checks its effects with a flash audit that reads a recording and counts flashes a second against WCAG 2.3.1, the guideline games follow. The game’s journal, its changelog and the audit itself took the guideline’s area to be a quarter of the screen.&lt;/p&gt;
&lt;p&gt;On October 17 the agent fact-checking a post about the shockwave compared that with W3C’s own explanation of the guideline. There, the limit applies to 25% of any 10-degree part of the viewer’s field of view, and W3C estimates that field as a 341 by 256 pixel rectangle on a 1024 by 768 screen. The game’s records had read that 25% as a quarter of the whole screen. The note went to the game session, which confirmed it and changed the audit that morning. It also added a test clip that the old math passed and the new math fails: 15% of the screen flashing five times a second. The game’s changelog credits the note to this site’s session.&lt;/p&gt;
&lt;p&gt;The fact check made an error of its own. It took the area to be the whole rectangle, about 11% of the screen, from a sentence in the explanation’s intent section, and the game session used that figure. On October 18 a review of the MipYip version of this post found that W3C’s page disagrees with itself, and that the guideline’s definition and its technique G176 limit flashing to a quarter of the rectangle: 21,824 square pixels, about 2.8% of the screen. The original audit had been about nine times as lenient as the rule it cited. The game session changed the audit again and audited every clip it had kept. Two effects failed at the corrected area and were changed that day: a hurt ship’s view at the strongest damage setting, and the title screen’s falling buildings, whose blasts now go off in pairs at least a second apart. Two of the title screen’s camera shots, which run fast past bright detail, still fail. The shockwave and the blast flare still pass on W3C’s reference screen. The audit covers the game’s own effects, and play itself can still flash over the limit, for example when the camera tumbles through a collapsing building, so the game shows a photosensitivity warning at every launch.&lt;/p&gt;
&lt;h2 id=&quot;catching-numbers-that-were-never-built&quot;&gt;Catching numbers that were never built&lt;/h2&gt;
&lt;p&gt;The game’s design documents hold proposals as well as decisions, and a proposal reads like a fact. The ship classes document lists the cannon’s stream at 16 rounds a second. That was a proposal, and the game fires 12. The fact check on &lt;a href=&quot;/devlog/rounds-that-never-showed-up/&quot;&gt;The muzzle flashed, but the rounds never showed up&lt;/a&gt; caught it before the post went out.&lt;/p&gt;
&lt;h2 id=&quot;reviewing-the-game-sessions-themselves&quot;&gt;Reviewing the game sessions themselves&lt;/h2&gt;
&lt;p&gt;On October 11 I asked the site session to review the game session’s work from September 29 to October 11. Four agents each read a slice of the game session’s transcripts. They found about 15 issues closed as fixed and then seen again in play, and the patterns behind them. The site session doesn’t write to the game’s repository, so it handed the findings over as an issue, and the game session put them into its rules the same day. Those are the rules in the post on vibe coding.&lt;/p&gt;
&lt;h2 id=&quot;where-it-falls-short&quot;&gt;Where it falls short&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It checks records, not code.&lt;/strong&gt; When a piece of code and its record agree and both are wrong, the fact check passes them. The flash audit was caught because its rule comes from a public standard that can be checked from outside the game.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It doesn’t see what I see.&lt;/strong&gt; The first still and clip for a post about erupting rubble were early physics tests, captioned in the game’s journal as the effect itself. I caught that, not the check, and the rule that every image shows what its caption says came from it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A correction helps only if it gets back to the game.&lt;/strong&gt; The flash audit was fixed that morning, but the design document still listed 16 rounds a second, two journal entries still described a quarter of the screen, and the rubble entry still captioned its physics tests as the eruption. Those went back to the game session as notes later the same day, and it corrected all four.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vibe coding means accepting what an AI writes without checking it. Here, the work is checked by tests, by reviewers that didn’t build it, by my play, and by an agent whose job is to describe it accurately in public.&lt;/p&gt;
</content:encoded><category>Studio note</category><category>claude-code</category><category>tooling</category><author>Alex van Rossum</author></item><item><title>Keeping the project out of vibe coding</title><link>https://trilithon.games/devlog/keeping-the-project-out-of-vibe-coding/</link><guid isPermaLink="true">https://trilithon.games/devlog/keeping-the-project-out-of-vibe-coding/</guid><description>Wreckwing changes faster than I can read it. Where that drifted toward vibe coding, the rules we added, and what the first play-through under them showed.</description><pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Wreckwing is built almost entirely by Claude Code sessions. I design the game, direct the work and play it. I don’t read the code. On October 10 the game’s repository took more than 26,000 new lines in one day, and no one read them line by line.&lt;/p&gt;
&lt;p&gt;Vibe coding usually means accepting what an AI writes without checking it by any means. By that definition this project isn’t vibe coding. Every piece of work starts as an issue with a stated condition for when it’s done, changes are judged by measurement, and when two fixes for enemies hitting walls made the numbers worse, we took them out. But speed makes it easy to drift. When the checking is done by tests and by play instead of by reading, anything the tests don’t measure goes unchecked. On October 11 I found out how much that could hide.&lt;/p&gt;
&lt;h2 id=&quot;recognizing-the-drift&quot;&gt;Recognizing the drift&lt;/h2&gt;
&lt;p&gt;Every enemy pilot in the game has a personality: how aggressive it is and how cautious. Personalities were drawn for every enemy and written to the logs. But only the experimental trained pilot read them, and the game flies the scripted one. For a week, every enemy in a level flew the same way, and nothing failed. We found it while measuring why enemies so rarely boosted or chased (&lt;a href=&quot;/devlog/enemies-werent-using-their-ships/&quot;&gt;Enemies weren’t using everything their ships can do&lt;/a&gt;). Code that nothing reads looks alive and isn’t.&lt;/p&gt;
&lt;p&gt;That led to a review of the game’s sessions from September 29 to October 11. It found a pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;About 15 issues had been closed as fixed and then seen again in play. Enemies spawning inside buildings had been closed three times. Floating rubble was closed with 8 pieces left, and a replay eight hours later held 72. The minimap closed on a test render that came out blank, then crashed on another Mac.&lt;/li&gt;
&lt;li&gt;Some problems took many rounds of fixes before they were solved or set aside: floating rubble 6, wall bumps 6, stalls in large cities 5, the water splash 4. Four stills the sessions had approved themselves turned out to be wrong.&lt;/li&gt;
&lt;li&gt;Of ten notes from a side session over three days, six were knock-on effects of a change that nobody had raised. Doubling ship toughness also halved what a falling building did to a ship, night arrived in one city in four with no way to turn it off, and cars skated across the new ponds.&lt;/li&gt;
&lt;li&gt;I was asked about 36 questions on one evening, and about 15 of them didn’t matter to me either way. On October 11, 33 issues sat waiting for me to play them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of that is accepting code without checking it. It is checking that had gaps, at a speed where the gaps added up.&lt;/p&gt;
&lt;h2 id=&quot;what-i-had-to-change&quot;&gt;What I had to change&lt;/h2&gt;
&lt;p&gt;These went into the game’s rules on October 11:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Every feature gets a test that it changes behavior.&lt;/strong&gt; The test runs the game with the feature and without it, or at two settings, and checks that what a player would see differs in the expected direction. A test that only shows the code runs doesn’t count. For the personalities, aggressive pilots now have to deal more than twice the damage cautious ones do. This rule came from an outside reviewer’s suggestion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A problem I report closes on my own case.&lt;/strong&gt; My flight, my city or my scene is kept in the repository as a test that fails before the fix and passes after. Without it, the fix is called unconfirmed, the issue stays open with a “Needs play” label, and it goes on my play checklist.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A smoke test before anything a player reaches is closed.&lt;/strong&gt; A clean build of the game flies a run level, the sandbox with a wave of enemies and wave defense, with no one at the keyboard, and reports spawns, frame rates, the HUD and crashes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two attempts, then measure.&lt;/strong&gt; After two failed fixes for the same problem, the next step is a measurement that isolates the cause and a research pass, not a third fix. On visual work, a separate model that didn’t make the change compares the stills with reference photos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check the consequences of shared numbers.&lt;/strong&gt; A change to a default or a balance value is run with and without it first, and its effect on the rest of the game is counted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer questions.&lt;/strong&gt; At most five at a time, each with a recommendation. Anything waiting on my play goes on one checklist a session, where I mark each item and the sessions read my verdicts back.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-its-working&quot;&gt;How it’s working&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The night glow.&lt;/strong&gt; Measuring the new neon city showed the same number of bright pixels by day and by night. Every glow had been clipped at the brightness of its own color, so the night lighting we had designed had never reached the screen. It was the same kind of failure as the personalities, found the same afternoon the rules went in. A test now checks it: a lit sign cabinet went from 2 bright pixels to 902.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enemies inside buildings.&lt;/strong&gt; On October 14 I flew into a city where an enemy started inside a building. This had been closed as fixed three times. The two-attempts rule meant the next step was a measurement. The flight recordings showed 5 of the run’s 19 enemies starting at exactly 158.4 m from me, the distance the game’s unchecked fallback uses. Rebuilding my city without a screen found the cause: the elevated freeway counted as one box around the whole road, 502,000 m², and every spawn point not near me lay inside it. Now each piece of road has its own box. My city keeps 37 of 160 spawn points open, where it had none, and it is kept as the test. The smoke test had flagged the same problem in two other modes that night. Once those spawns worked, it caught a new one: a fighter warping in on top of a drone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The house windows.&lt;/strong&gt; An issue about breakable house windows and doors stayed open because one part, a warehouse door, couldn’t be reproduced without a screen. Before, it would have been closed. It closed after I confirmed it in play.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first play-through under the new rules was on October 14. I flew 31 items on the checklist, and 22 held up. Of the other nine, seven were design and tuning, such as more impact for ship deaths and a role for the Sniper. Enemies flying into walls was better but not gone, and lit windows that change as you approach a building were a new defect. Separately, one old fix came back: the spawns above, closed three times under the old rules.&lt;/p&gt;
&lt;h2 id=&quot;still-unproven&quot;&gt;Still unproven&lt;/h2&gt;
&lt;p&gt;This is one play-through against two weeks of history. The count that matters is how many fixes come back over the next several play-throughs, against about 15 in the first two weeks.&lt;/p&gt;
&lt;p&gt;The rules also missed something. Two enemy kinds, the Bruiser and the Dozer, were built to fight up close. In my play they seemed to do nothing. A measurement found they did fight, but their guns did the least harm of any enemy. The balancing tests hadn’t flagged it. It was found two days after it was built, where the personalities took a week, but it was found by me in play, not by a test. The fix also went in without my flights kept as its case; a measurement stood in for them. &lt;a href=&quot;/devlog/eight-kinds-of-enemy/&quot;&gt;Pitting a dodging test player against eight enemies&lt;/a&gt; has the numbers.&lt;/p&gt;
&lt;p&gt;Some of these rules are enforced by scripts, such as the commit checks, the smoke test and the release gate. Most are followed by the sessions because they are written down, and nothing stops a session from skipping one. The checklist and the closing comments are how I see whether they did.&lt;/p&gt;
&lt;p&gt;I’ll write again after more play, with the number of fixes that came back.&lt;/p&gt;
</content:encoded><category>Studio note</category><category>claude-code</category><category>tooling</category><author>Alex van Rossum</author></item><item><title>Agents with jobs</title><link>https://trilithon.games/devlog/agents-with-jobs/</link><guid isPermaLink="true">https://trilithon.games/devlog/agents-with-jobs/</guid><description>The Claude Code agents that build the game, the asset studio and this site, the contractors they hire, and which model does which job.</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;four-kinds-of-agent&quot;&gt;Four kinds of agent&lt;/h2&gt;
&lt;p&gt;Almost all of our code is written by Claude Code, and the work is split between agents that each have a job, a boundary and a way to hand work on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The main agent&lt;/strong&gt; works in the game’s repository. It owns the game and its engine, and it is the only agent that commits to the game. Every commit passes the game’s checks first and names its Linear issue. It reviews the studio’s work and closes the studio’s issues, and it keeps the dev journal some of these posts started from.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The studio agent&lt;/strong&gt; works in the asset studio’s repository. It designs ships as data files, with props and vehicles to follow, renders them for review, and checks them against the engine and the flight rules. It never commits to the game. It exports a checked file, and the main agent commits it after the game’s own checks pass. The two share a build lock, so they never build at the same time, and the studio won’t build while the game is being played.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The site agent&lt;/strong&gt; runs this website. It owns the site, its Linear project and its hosting. It never writes to the game’s or the studio’s repositories. When something needs changing there, it leaves a Linear comment or a proposed change for that agent. It merges its own work once its checks pass, and it deploys only with a go-ahead. The main and studio agents will write devlog posts here by pull request, and the site agent reviews and merges them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contractors&lt;/strong&gt; are hired by any of the three for one scoped job, and they leave when it is done.&lt;/p&gt;
&lt;h2 id=&quot;hiring-a-contractor&quot;&gt;Hiring a contractor&lt;/h2&gt;
&lt;p&gt;When something would fill the main agent’s context with material it doesn’t need to keep, we tell the main agent to go hire someone for it. Research, a sweep through dozens of files, or chasing a stray thread through old issues all count. A contractor is a subagent or a separate headless Claude Code session. It works in its own context, reads and searches as much as the job needs, and hands back a report. The main agent keeps the conclusion and the plan, and its context stays clean for the work in front of it.&lt;/p&gt;
&lt;p&gt;Contractors read and report. They don’t commit, and they don’t build or run the game while it is being played. The agent that hired them checks the work and commits it.&lt;/p&gt;
&lt;p&gt;One rule sends work to contractors on purpose. When a fix hasn’t landed after a round or two, or something feels wrong and is hard to put into words, the main agent stops iterating and hires a research pass. The pass looks at how others solved the problem in games, papers, engine docs and talks, turns the description into candidate causes and options with references, and brings them back before more attempts go in.&lt;/p&gt;
&lt;h2 id=&quot;which-model-does-which-job&quot;&gt;Which model does which job&lt;/h2&gt;
&lt;p&gt;Each job goes to the cheapest model that gets it right.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sonnet&lt;/strong&gt; is the default: research passes, sweeps through files and docs, extraction, mechanical ports, running and reading benchmarks, and drafting from a settled outline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opus&lt;/strong&gt; takes the jobs where a wrong answer is expensive and hard to notice: architecture and migration plans, reviews of risky changes, load-path and determinism debugging, and independent checks of finished work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The most expensive tier&lt;/strong&gt; is used rarely, and only with a stated reason.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The test we apply is whether a plausible wrong answer would survive review. If it would, the job gets a stronger model.&lt;/p&gt;
&lt;h2 id=&quot;in-numbers&quot;&gt;In numbers&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Research documents:&lt;/strong&gt; 41 of the 61 documents in the game’s docs folder were written by research passes. They cover glass, water, fire and smoke, enemy pilots, ship classes, city layout and naming, among others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commits:&lt;/strong&gt; 62 of the game’s 761 commits mention a contractor, a research pass or a worktree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One review:&lt;/strong&gt; an Opus review of a night’s gameplay code found eight problems, all fixed. The largest was that the enemy pilots’ sensing rays started inside their own hull.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;some-of-the-jobs&quot;&gt;Some of the jobs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The name.&lt;/strong&gt; Three naming research passes checked candidates against game stores and domains. The third listed Wreckwing as clear on every check it ran, and we picked it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The studio’s plan.&lt;/strong&gt; A headless Opus session wrote the plan for the asset studio from the game’s code. The main agent spot-checked its claims about the code, and the plan was approved the same day.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A night’s review.&lt;/strong&gt; The eight problems above came from a read-only review the main agent ran after a night of gameplay work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A look that reads as modern.&lt;/strong&gt; Four headless Sonnet research passes studied materials, lighting, geometry and atmosphere, and a synthesis turned them into a staged plan.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Parallel agents.&lt;/strong&gt; We run more than one agent at once, each in its own git worktree, like a small team merging its work. The game’s Linear record moved to the repository’s shared git directory, so parallel agents see the same list of closed issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ships from the studio.&lt;/strong&gt; On October 10 the studio agent exported the textured Sniper, then the Skyhammer, the Brute, the Wasp and a copper Raven. The main agent committed each one after the game’s checks passed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;These posts.&lt;/strong&gt; The back catalog on this devlog was built the same way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;three Sonnet contractors mined the game’s changelog, its docs and Linear for stories;&lt;/li&gt;
&lt;li&gt;five Sonnet contractors drafted posts;&lt;/li&gt;
&lt;li&gt;four Opus contractors checked every number and credit against the sources, independently of the drafters, and corrected what didn’t match;&lt;/li&gt;
&lt;li&gt;another contractor rendered the ads and checked the game clips frame by frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The site agent kept the plan, read the reports, checked the media itself, and merged the work.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-it-holds-together&quot;&gt;How it holds together&lt;/h2&gt;
&lt;p&gt;Every agent works from the same Linear issues and the same rules written in its repository, so a job can move between agents without a conversation. &lt;a href=&quot;/devlog/why-linear-runs-the-project/&quot;&gt;Why Linear runs the project&lt;/a&gt; covers that side.&lt;/p&gt;
</content:encoded><category>Studio note</category><category>claude-code</category><category>tooling</category><author>Alex van Rossum</author></item><item><title>Why Linear runs the project</title><link>https://trilithon.games/devlog/why-linear-runs-the-project/</link><guid isPermaLink="true">https://trilithon.games/devlog/why-linear-runs-the-project/</guid><description>A game built almost entirely by Claude Code sessions, and 463 Linear issues in twelve days. Why every piece of work starts and ends in Linear.</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate><category>Studio note</category><category>claude-code</category><category>tooling</category><author>Alex van Rossum</author></item><item><title>Why we started over</title><link>https://trilithon.games/devlog/why-we-started-over/</link><guid isPermaLink="true">https://trilithon.games/devlog/why-we-started-over/</guid><description>The first attempt at a building-destruction game failed at about six buildings. This is what we scoped for the restart and what carried over.</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;the-first-attempt&quot;&gt;The first attempt&lt;/h2&gt;
&lt;p&gt;Before the current project there was an earlier one with the same idea: a toy for wrecking buildings, in the spirit of an older destruction game we like. It built its buildings from voxels (small cubes), drew them with Three.js in a browser engine wrapped in a desktop shell, and ran its physics single-threaded, in WebAssembly, on the main thread.&lt;/p&gt;
&lt;p&gt;It was an experiment that kept growing until it was unusable, and with about six buildings at once it started to fail. Part of the trouble was the voxels. They made the math easy, but the voxel compute and the physics could not keep up. The project had also tried to build too much at once, including a grand story, when it should first have been a place to take a building down.&lt;/p&gt;
&lt;h2 id=&quot;what-we-scoped-for-the-restart&quot;&gt;What we scoped for the restart&lt;/h2&gt;
&lt;p&gt;On September 28, we decided to try again with a narrower goal and set these terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first stage is an engine that holds up. Custom or existing both count, as long as it can carry what we want to do with it.&lt;/li&gt;
&lt;li&gt;No Unity, which was too much for this project, and no Java, which is not efficient enough.&lt;/li&gt;
&lt;li&gt;The game is Mac only, and it is for us to play. If anyone else gets to play it, that is a bonus.&lt;/li&gt;
&lt;li&gt;Prefer structural destruction to voxels: buildings that break along their structure.&lt;/li&gt;
&lt;li&gt;The first playable is one procedurally generated building at a time that can be respawned. A tiny house, a set of silos, a very tall skyscraper or a stadium would all do, each with real structure and debris that looks plausible when it comes down.&lt;/li&gt;
&lt;li&gt;Later, the player flies, with rifles, bombs and remote charges, and the world grows toward a larger one with more buildings.&lt;/li&gt;
&lt;li&gt;Not wanted: people in the world, automatic defenses, a grand story.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The planning that followed, recorded in the brief, added two decisions. Buildings should fracture and render the way real buildings would, and the architecture should leave room for terrain, trees and water later.&lt;/p&gt;
&lt;h2 id=&quot;what-carried-over&quot;&gt;What carried over&lt;/h2&gt;
&lt;p&gt;The restart kept the earlier project’s measurements. The architecture notes list them as rules.&lt;/p&gt;
&lt;div class=&quot;tl-table-scroll&quot; tabindex=&quot;0&quot; role=&quot;region&quot; aria-label=&quot;Table, scrolls sideways&quot;&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Earlier measurement&lt;/th&gt;
&lt;th&gt;Rule in the restart&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A voxel collider on a moving body cost 98 times a plain box&lt;/td&gt;
&lt;td&gt;No mesh or voxel colliders on moving bodies. Debris is a convex hull (a solid with no dents) or a group of them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;800 resting pieces cost 0.06 ms a step, against 11.87 ms awake&lt;/td&gt;
&lt;td&gt;Resting debris is nearly free and awake debris is not, so settling is a feature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;About 3,300 draw calls, roughly 70% of them in the shadow pass&lt;/td&gt;
&lt;td&gt;All opaque geometry is drawn in one call per pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts with the static world cost about 30 µs each in the full scene, against 1.4 µs alone, and the cause was never found&lt;/td&gt;
&lt;td&gt;Measure those contacts instead of assuming them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;A step here is one tick of the physics simulation, 1/60 s. A draw call is one batch of geometry sent to the graphics chip.&lt;/p&gt;
&lt;p&gt;We added a lesson about how the earlier project was measured. It was tuned on clean scenes without rubble, and slowed to a crawl once debris built up. The restart’s rendering gate draws six intact buildings with 20,000 fragments lying around them, so a result includes rubble.&lt;/p&gt;
&lt;h2 id=&quot;what-the-restart-measured-differently&quot;&gt;What the restart measured differently&lt;/h2&gt;
&lt;p&gt;The same kind of test, rubble in a building compared with rubble on flat ground, cost about 20 times as much in the earlier project. On the physics library the restart uses, it costs 1.3 times as much. The earlier project never found why its number was so high, so the comparison shows the new stack does not have the problem. It does not explain the old one.&lt;/p&gt;
&lt;p&gt;The decision record also lists what was considered and set aside, with the reasons as of September 29:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unity, which we ruled out.&lt;/li&gt;
&lt;li&gt;Godot, which is built around its editor and had open reports of freezes in its Mac renderer.&lt;/li&gt;
&lt;li&gt;Bevy, kept as the fallback if the flat-ground numbers failed.&lt;/li&gt;
&lt;li&gt;NVIDIA’s Blast and PhysX 5, which do not run on macOS.&lt;/li&gt;
&lt;li&gt;Apple’s SceneKit and RealityKit, which have no fracture support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stack that stayed is Swift and Metal with the Jolt physics library, compiled from source.&lt;/p&gt;
</content:encoded><category>Postmortem</category><category>physics</category><category>performance</category><category>tooling</category><author>Alex van Rossum</author></item></channel></rss>