Lyran Defense
A painterly tower-defense campaign of the Lyran Wars — twenty-one stands along the road to the evacuation fields, a procedural orchestral score, and one deterministic Go simulation that plays the same battle, bit for bit, on iPhone, iPad, Mac and Apple TV.
App Store · iPhone · iPad · Mac · Apple TV · one free download
A war you cannot win, painted one board at a time.
The Lyran homeworld is falling. The evacuation ships are loading. Between the rift and the beacon stands one thing: you. Lyran Defense is not a war you win — it is a year of buying hours. Twenty-one stands along the road to the evacuation fields, each held long enough for another ark to clear the ground, and then The Long Watch: an endless mode where the only score that matters is how many more ships got away because you were still standing.
Every board is a hand-illustrated painting with its own biome and its own way of turning against you — mires that slow the column, rifts that tear open mid-wave, roads that fork and cross. Eight tower families upgrade into an elite form, and three of them fork at the top into one of two shapes, permanently. A fully procedural orchestral score is composed on the device as you play: it rises with the wave, thins when the line thins, and mourns the levels you lose.
It is a personal project shipped with the same discipline as the client work: free and complete, fully offline, no ads, no account, nothing collected — and every one of those sentences is checked at deploy, not merely written down.
“I am no soldier. None of us are.” Warden Sael · the first watch
The campaign, the line, the song.
Twenty-one stands and an endless watch
A story campaign across the falling homeworld with hand-written briefings before every stand, four difficulties from a calm first watch to an edge that will take your soul, and per-level records: fastest clear, least gold, fewest towers, most lives held.
Towers that fork, and never un-fork
Arrow, frost, ember, mortar, venom, lance, banner and mender. A Longbow becomes a Starshot Bastion or a Skypiercer — never both. Target priorities per tower, special attacks aimed by hand, and ground that refuses some towers outright and tells you why.
Every level is a painting you are allowed to lose
Twenty-two illustrated boards, each its own biome and landmark, painted taller than wide so a phone held upright gives the battlefield the whole screen. Weather fights you: rain and snow slow the field and hide the road.
A score composed as you play
No looped tracks. The orchestral score is generated on the device from the state of the battle — rising with the wave, thinning with the line — and The Score keeps a chronicle of the war's music, earned by holding it.
A war codex that fills as you meet it
The Journal writes down every tower, creep and doctrine you encounter, in the voice of a warden who never expected to need any of it. The Gallery keeps the paintings you have earned. Twenty-six Game Center achievements, optional and signed-in-or-not.
Free, complete, offline, quiet
No ads, no account, no analytics, no third-party SDKs. The whole game is on your device; the only network it ever touches is Apple's own, and every bit of that is optional. “Light the Beacon” is a one-time supporter purchase that unlocks nothing you need to win.
One simulation, four machines.
The game is a pure-Go simulation with no renderer, no I/O and no floating point: integer math in milli-cell fixed point, a seeded PCG random stream, and no map iteration anywhere in game logic. That is what makes it deterministic — the same seed and the same commands produce the same state, byte for byte, on every CPU it has ever run on — and determinism is what makes everything else cheap: a battle is exactly replayable, a victory is exactly earned, and a bug report is a seed and a command log.
The sim's own rule checks are the integrity layer: every command goes through sim.Apply,
which refuses anything the rules refuse, so there is no second copy of the rules in any client to drift. Above it sits one Swift
codebase building four Apple targets — the phone, the iPad, the Mac and the television are cuts of the same game, not ports of a
phone game — and the marketing site's claim that it "runs the same battle on each" is a golden-hash test, not a sentence.
Tech.
Simulation
- Go, pure — no renderer, no I/O, no floats
- Milli-cell fixed-point math, seeded PCG RNG
StateHash()+ committed golden hashes in CI- Content as JSON via
go:embed, validated at load; bad data fails CI - Terminal playtest client drives the sim exactly as a renderer would
Apple Clients
- SwiftUI + SpriteKit, one codebase, four targets
- iOS · iPadOS · macOS · tvOS, Swift 6 strict concurrency
gomobileXCFramework binding, JSON over the FFI- Game controllers (PlayStation, Xbox, Switch) · Siri Remote focus cursor
- StoreKit, Game Center, iCloud key-value store — all optional
Sound & Art
- Procedural orchestral score, composed on device from battle state
- 22 hand-illustrated boards, painted towers and creeps — no sprite grid
- One icon master → every platform cut, enforced by a byte-compare
- Story-art ledger: every named plate must ship on every client
Shipping Discipline
- Release gates read the signed archive — entitlements asked of
codesign, not of the source - Marketing claims checked against the store both ways: a badge for a platform that does not serve fails, and so does a live platform the page hides
- Every rune in the shared string tables must have a glyph in every font that draws it
- Screenshot walks across all four targets; a caption that covers the HUD refuses to upload
The parts worth writing down.
Determinism is the test oracle
Because the sim has no floats, no I/O and no map iteration in game logic, a level's final state hashes to the same value on every machine. Those hashes are committed as golden files and checked on every build, and a cross-platform job proves the sim is bit-identical on a second host. A balance change that alters a hash is a decision; one that alters it by accident is a bug, and the diff says which.
The board is taller than it is wide
Most tower defense is designed landscape and cropped for phones. These boards were painted portrait from the first sketch, so a phone held upright and an iPad in portrait both give the battlefield the whole screen, and the television letterboxes a painting instead of stretching one. The camera, the HUD and the touch targets all follow from that one decision.
A score composed, never replayed
The orchestral score is generated on the device from what the battle is doing — the wave building, the line thinning, a tower lost — rather than crossfading between loops. Two identical sources are kept in lockstep by a byte-compare in CI, so the music one client hears is the music every client hears.
Records merge; they never overwrite
Progress rides the player's own iCloud between devices, and the merge is per record: a fastest clear set on the Mac and a no-loss run set on the phone both survive, and a device that has been in a drawer for a month contributes what it earned instead of being flattened by one that has not. It is the player's iCloud, not a server of mine — and the whole game plays signed out of it.
Ask the font, the only party that knows
A shared string drawn by two clients with different font stacks can come out as a tofu box on one of them — string correct, JSON valid, layout unchanged, and a text-level check reports it as fine. So a test sweeps every rune in the shared tables against every font that draws them and asks the font itself whether the glyph exists. Exemptions are by file path, never by rune, and an exemption that matches nothing fails.
Ship gates read the artifact, not the intent
The entitlements that make iCloud and Game Center work live in source files that were correct all along; what matters is whether they reached the signed archive. So the release lane asks codesign what the archive actually carries before anything is submitted, and the marketing site's platform badges are reconciled against the live store in both directions at deploy — a claim about what ships is checked against the thing that ships.