Seat Time outgrew its name—and some merges went missing
Our browser driving game, built with our crew of AI coding agents, had outgrown the name Racing Sim. By the second week of October 2026, it had jobs, a wallet, licences and a little city. We wanted a name that covered more than racing. Underneath that growth, though, some work marked “Merged” had never reached the game.
The rename itself went smoothly. Old links still led to the game, and saved progress survived. The harder problem was a car missing from the selector despite its completed merge. That sent us beyond the reassuring status on a pull request—the proposal we use to review and integrate code.
The racing sim grew a wallet and a city
The game was accumulating a life around the driving. Our development records show a licence system, a tuning shop, part-time jobs, mileage and a wallet. Paid plans and Miniature Rio city jobs joined the scope. Before the split, the list also included a credit ledger, initially switched off, a dealer, rentals and missions.
“Racing Sim” described the driving but said little about all that surrounding activity. We wanted room for trophies, tuning and the time spent getting better behind the wheel. Seat Time fit that direction: we chose it for the racing expression about hours behind the wheel. It kept driving at the centre without making every activity sound like a race.
The new name did not resolve the scope question by itself. Someone arriving to race and someone following the jobs-and-money loop had different reasons to open the same game. Renaming gave us a better label. We still needed to decide what belonged behind it.
We changed the name without resetting progress
On 11 October, we renamed the game to Seat Time. The file relocation was a moves-only commit—a recorded change containing 1,412 file moves. Keeping those moves separate made the mechanical part of the rename explicit. The work also changed the language-message prefixes and redirected the old game addresses.
Old /games/racing-sim links redirect to the new destination, retaining the locale and query parameters. We used HTTP 308, the permanent-redirect status defined in RFC 9110. The standard explains the status; keeping our language and query information was part of our implementation.
We deliberately left the saved-data keys, schema tags and database table names alone. Those names connect the game to progress already stored in a browser or database. Making every internal name match the new sign would have added a data migration to a naming change. Published content archives also kept their historical names.
That left an untidy-looking boundary inside the project, but it was intentional. The public name changed while the identifiers carrying saved progress stayed stable. We were trying to preserve the player's place in the game, not win a search-and-replace contest.
Three days later, we separated racing from life
On 14 October, we split the scope into Seat Time and Seat Life. Seat Time became the racing edition, with a team garage and a one-make ladder: 27 events covering 24 racing cars. Seat Life became the economy hub around a racer's life. Both used one engine and one save profile.
The split gave each edition a clearer purpose without requiring separate progress. Racing could have its own entrance, while the jobs-and-economy loop had another. The shared engine mattered because these were two ways into related driving systems, rather than a decision to rebuild the simulation twice.
Those counts describe the split at the time. The tidy division on paper did not tell us whether every accepted change had reached the shared code.
Our agents' checkouts filled the disk
Parallel work had a physical cost before we understood the delivery problem. Around 12–13 October, the agents' checkouts occupied 413 GB and left 4 GB free. Each task could look modest on its own. Together, the copies consumed the disk we needed to keep building.
We moved toward sparse worktrees. A Git worktree provides another working directory attached to the same repository. Sparse checkout limits the tracked files present there to a selected subset. Together, these let us give an agent the relevant part of the project without another full working copy.
Our automated checks also ran out of disk during full checkouts. On 13 October, we rewrote repository history to remove about 1.1 GB of generated output. That addressed stored material in history, a different problem from how many files each agent needed in its working directory.
The rewrite had a lasting cost for our records. The first 150 pull requests no longer belonged to the rewritten main history. Their retained pull-request pages became the primary record for that early work. We could not later treat missing ancestry alone as evidence that those changes had been lost.
A merged car was missing from the selector
The Formula Club 1500 Aero gave the merge problem a concrete shape. Our recovery record describes a fresh guest seeing 24 cars, with no Aero in the list. Its pull request had merged, but into a branch whose own work had already been integrated. The recovery brought the original history into main.
A branch is a separate line of development; a merge combines work into a chosen destination. Main is our shared integration line. Those definitions sound mundane until the destination is wrong. “Merged” says that a proposal reached its target branch, not that it reached main—or the build a player opens.
The completed proposal and the guest's car list disagreed.
The destination stayed behind after its parent moved on
The trap came from stacked pull requests. Imagine change B built on change A, with B targeting A's branch. A merges into main first. If B then merges into A's branch, the new work lands somewhere that has already finished its trip to main. Nothing about that later merge automatically repeats the earlier integration.
Stacking was not itself proof of a mistake. Our 3 November query counted 106 non-main destinations among 516 merged pull requests. Those included intentional batch branches as well as stacked work. The problem was treating the destination as safe after the parent had already merged.
The same query found 13 proposals merged into branches whose own proposals had already merged into main. The gaps ranged from 1 second to about 54 minutes. That is a count of the timing pattern, not 13 proven, permanent losses. Some work was recovered or brought in another way; the query alone cannot decide which.
This changed what we needed to ask at merge time. “Is this ready?” was incomplete. We also needed “Where is it going now?” A destination that made sense when the proposal opened could be wrong by the time we accepted it.
Flash-forward: twenty seconds on 24 October
A later case makes the sequence visible down to the second. On 24 October, the parent change reached main at 15:36:20 UTC. A kei-car physics change merged into that parent's branch at 15:36:40 UTC. Twenty seconds separated them, and main stayed on physics version 6 instead of receiving version 7.
We later brought that physics change into main. The records do not say how the miss was discovered. They do show that the merge order was enough to leave main on the old physics. This happened after we had already added a delivery check on 17 October.
The short gap is what makes this case useful. There was no need for a forgotten branch to sit untouched for days. The parent could finish, the child could follow almost immediately, and the child could still miss main. A correct destination was part of finishing the work.
Recovery meant bringing back the work, not changing a label
We recovered more than the missing car. A change consolidating our deployment publisher had landed 14 seconds after its parent merged; it was brought back separately. Accepted account, practice and native-app journeys also needed recovery, along with the standalone Challenge Lab. Each case required finding the actual work and getting it into main.
Another dated flash-forward belongs on 31 October. We recovered the learned driver's Watch demonstration, which had merged into its parent's branch 10 seconds too late. That recovery used a real merge preserving the original history. It let the delivery check recognize the recovered work as part of main's ancestry.
Not every entry had a recovery to tell. The Hall of Fame demonstration assessment was still recorded as not delivered when we last checked (3 November). It had appeared in the 17 October audit.
One rule, and a check that can say no
Our rule became simple: retarget a stacked pull request to main before merging it. Never merge it into a branch that has already merged. GitHub's base-branch documentation describes changing that destination. It also warns that the displayed commits and review comments can change, so retargeting still needs attention.
The delivery check added on 17 October asks whether each covered merged proposal's final commit is an ancestor of main. An ancestor is a change reachable through main's recorded history. If it is absent, the check fails unless a reviewed exception records equivalent work or an acknowledged non-delivery.
“Equivalent” means the work arrived another way; “not delivered” keeps a known omission on the list. Neither should be confused with proving the original history reached main. The rewritten early history also needs separate treatment, rather than turning the first 150 proposals into false alarms.
The check can correctly fail during batch work, while accepted changes still wait on a shared integration branch. We have to finish that integration rather than teach the check to ignore the inconvenience. Our GitHub Actions checks were billing-blocked, so this gate ran with our local automated checks.
The later physics miss happened despite the new rule. What changed was our definition of finished: a merge status needed a destination and delivery evidence beside it. Our agents wrote the work and helped recover it; we directed the work and owned the merge decisions.
The next question is what players actually receive
The rename preserved old links and saved progress. The split gave racing and the economy loop clearer homes. Neither could make a merged proposal proof of delivery. We had to follow the work into main, including the things we had already called finished.
Drive the current Seat Time build.
If “merged” doesn't mean “in main”, does “in main” mean “live”?
Previous: Rain and the creeping car. Next: Every merge is a release.
Notes and sources
Sourced facts and chosen design decisions — Our development records document the 11 October rename, 1,412 file moves, preserved storage identifiers and redirects. They also document the 14 October split, its 27 events and 24 racing cars, shared engine and save profile. Scope-growth details come from recorded change titles, not a fresh feature audit. All dates in this account are in 2026.
Measured — Our development records report 413 GB of checkouts and 4 GB free around 12–13 October. The 13 October rewrite removed about 1.1 GB of generated output. Our 3 November query joined proposal destinations, source branches and merge times: 106 of 516 merged proposals targeted non-main branches; 13 matched the late-merge pattern, from 1 second to about 54 minutes. This was not a count of permanently missing features.
Sourced facts — Recovery records support the Aero's missing-car account, the 24 October physics sequence, the publisher and journey recoveries, and the Watch recovery on 31 October. The dates of the Aero's loss and recovery are unknown. The 17 October audit and delivery-check records establish the safeguard and the unresolved Hall of Fame entry, last checked on 3 November. No new hardware drive or current live-build assessment was performed for this account.
Public references — RFC 9110, 308 Permanent Redirect, Git worktree, Git sparse checkout, and GitHub: changing a pull request's base branch explain the public mechanisms. They do not independently verify our development history.
Our interpretation — “Merged” describes a proposal's status, not the product's contents. AI assisted this article's writing and localization; the historical facts come from our retained development records.