When we decided that every new puzzle game at the studio would run on one codebase, the first question was not "which patterns" but "where are the walls". A framework that lets game code reach into everything is just a large game. The value comes from boundaries that hold when the tenth mechanic arrives and nobody remembers the first.
This is an engineering write-up of how the CMZ Puzzle Framework is structured in Unity. It assumes familiarity with assembly definitions, ScriptableObjects and the usual mobile SDKs.
Package boundaries
The project is split into assemblies with strictly one-directional dependencies:
- Core — shared utilities, event bus, save system, localization, timers. Depends on nothing.
- Puzzle.Contract — the interfaces a mechanic must implement. Depends on Core.
- Puzzle.Mechanics.* — one assembly per mechanic (BoardSort, ConveyorSort, ArrowLogic, CrowdJam, BlockBlast …). Depend on Contract and Core only. They do not know the meta layer exists.
- Meta — progression, rewards, streaks, events, leagues, collections. Depends on Core and Contract; never on a specific mechanic.
- Monetization — ads mediation wrapper, IAP wrapper, placement configuration. Depends on Core.
- Tracking — analytics client, event taxonomy, batching. Depends on Core.
- Game — the thin per-title assembly: theme, art, level data, configuration. Depends on everything.
The rule that matters most: mechanics never reference Meta, Monetization or Tracking directly. They raise events through Contract; the framework subscribes. This is what lets a new mechanic ship without touching (or breaking) the systems around it.
The mechanic contract
The contract is intentionally small. A mechanic provides:
LoadLevel(LevelData)andUnload()- An input surface the framework routes touches into
- Events:
LevelStarted,MoveMade(MoveInfo),LevelWon(WinInfo),LevelFailed(FailInfo),HintRequested - A
BoosterPortthat accepts booster effects the mechanic supports (undo, shuffle, extra slot…) and declares which it does not
Everything the meta layer and tracking need — attempts, time, moves, booster use, fail reason — flows through those events. If a mechanic wants a booster that does not exist yet, it declares a new booster type in Contract, and the meta and shop systems handle it generically.
Keeping the contract narrow was harder than it sounds. Every mechanic tempts you to add "just one" method. We resisted by asking whether the meta layer would ever need to call it; if the answer was "only this mechanic cares", it stayed inside the mechanic assembly.
Configuration-driven meta
The meta systems are built as runtime services configured by ScriptableObjects and remote config, not as scenes or prefabs a game copies and edits. A title enables a win-streak system by adding a configuration asset that describes thresholds, rewards and reset rules. The service subscribes to LevelWon / LevelFailed from the contract and does the rest.
The same applies to time-limited events, leagues, reward tracks and collections. Because the configuration is data, most of it is also exposed through remote config, so the live-ops team can adjust an event's parameters — or switch it off — without an app release.
The UI for these systems lives in Meta as themed prefabs with slots the per-title Game assembly fills with art. That gives each title its own look while keeping the logic shared.
Tracking for free
The Tracking assembly subscribes to the contract events and emits a fixed taxonomy: level start/win/fail with attempt index, duration, moves and booster usage; economy transactions with source and sink; ad and IAP events with placement and product identifiers; session boundaries. Titles add events only for genuinely title-specific features.
This is the single decision that made our analytics stack possible. Because every game emits the same events with the same meaning, the dashboards are built once, and a new title is fully covered from its first build.
Events are batched on-device with offline buffering and flushed on a timer and at session end. The batching layer is written to survive being killed mid-flush, which is the normal case on mobile.
SDK isolation
Ads, IAP, attribution and analytics SDKs change often and break at inconvenient times. Each is wrapped behind an interface in its own assembly with a null implementation used in the editor and in tests. Mediation networks are configured in data, and placements are named constants shared with the Tracking taxonomy, so a placement's revenue and its effect on the session can always be joined.
The practical benefit: an SDK upgrade touches one assembly, compiles or fails in isolation, and can be validated on a device without rebuilding the mechanics.
Mobile performance budgets
The framework enforces a few budgets that every title inherits:
- Startup to first interactive under a fixed target on a mid-range Android reference device, achieved by deferring SDK initialization and loading the first level scene additively.
- Frame time headroom checked in CI on the reference device with the profiler, per mechanic.
- Build size budget with Addressables for chapter content beyond the first, and texture compression presets per platform.
- Allocation discipline in the gameplay loop: pooled objects, no per-frame allocations, pre-sized collections.
Budgets are boring until the day a new mechanic doubles frame time on a device none of the team owns. Having them in CI turns that into a failed build instead of a one-star review.
What we would do differently
Two things. First, we would have introduced the null SDK implementations from day one; retrofitting them cost more than writing them would have. Second, we under-invested in editor tooling for level data early, and designers paid for it with slower iteration. Level tooling is framework code, not per-game code.
Summary
Walls first, patterns second. A minimal mechanic contract, one-directional assembly dependencies, data-driven meta, tracking on the contract events and SDKs behind interfaces are what allowed fourteen puzzle games to ship on one codebase in under a year — and what lets the fifteenth start at 60–70% done.
If you are building or evaluating a Unity framework for a portfolio of games, our engineers are happy to compare notes. Get in touch.





