Lab · Aug 2026
Sprout Siege
An idle farm game for iOS, built around a simulation core that never touches the screen.

- 115
- Swift files across a package and a thin app target
- 268
- tests in 29 suites, running headless
- v7
- save format, every version with a migration path
One rule
The package holding the game cannot import a user interface framework. No SwiftUI, no SpriteKit, no UIKit. Economy, waves, upgrades, prestige and saves all live in there and run headless under swift test. The app target stays thin: menus and HUD in SwiftUI, the combat board in SpriteKit, and no game rules in either. If a change needs the core to know about the screen, the change is wrong.

Deterministic by construction
The simulation is tick based rather than frame based. It advances by an interval, and every random number comes from an injected seeded generator, so the same seed and the same inputs produce the same result every time. Offline progress is not a second code path. Coming back after two days is the same call with a larger number. Balance lives in JSON rather than Swift, so tuning the game never requires touching code.
What that caught
Partway in, the structure surfaced a design problem I had not seen. Offline income was being generated by tower defense waves fighting themselves in an empty room. The player came back to a battle they had made no decisions in, which is neither believable nor good idle design.
The fix was structural. The farm became the idle spine: it runs in real time, and nothing wilts or expires or is ever lost while you sleep. Tower defense moved to being the first side game, short, active, and something you have to be present for, where you can lose what you were carrying. Two layers instead of one, with the passive half where passive actually makes sense.

Swift 6, iOS 17 and up. Building clean, running in the simulator.