A reusable progression engine that turns player performance into configurable XP, levels, and unlocks across games and apps. π
ProgressionKit is a pure Swift package for apps and games that need deterministic progression logic without coupling progression rules to storage or UI frameworks.
It models:
XPgain from successful performance.- Player levels derived from total XP.
- Track-scoped mastery across distinct content.
- Tier unlocks such as
beginner,intermediate, andadvanced.
The package is deliberately content-agnostic. Host apps decide what a track, content item, and tier mean, then feed those identifiers into ProgressionKit.
PKEngine: applies a progression event to a profile and returns the updated profile plus derived progress values.PKProfile: persisted progression state for a player.PKConfig: tunable progression rules such as level size, XP reward, tier order, and unlock thresholds.PKEvent: a single outcome emitted by the host app.PKUpdate: the result of applying one event.
flowchart TB
subgraph HOST["Host App/Game"]
EVENTS["Performance Events"]
STORAGE["Storage Layer"]
UI["UI / HUD / XP Bar"]
end
subgraph PK[" "]
ENGINE["ProgressionKit"]
PROFILE["PKProfile"]
CONFIG["PKConfig"]
UPDATE["PKUpdate"]
end
EVENTS --> ENGINE
CONFIG --> ENGINE
ENGINE --> PROFILE
ENGINE --> UPDATE
PROFILE --> STORAGE
UPDATE --> UI
Import the package and create an initial player profile:
import ProgressionKit
let profile = PKProfile()Create an event whenever the player finishes one unit of content:
let event = PKEvent(
contentID: "lesson.greetings.001",
trackID: "japanese-basics",
tierID: "beginner",
wasSuccessful: true
)Apply the event to the profile:
let update = PKEngine.apply(
event: event,
to: profile
)update is a PKUpdate value that contains the updated PKProfile and derived progression values your app can render immediately.
Common PKUpdate values you will typically use:
update.profile: persist this as the newPKProfile.update.playerLevel: current player level.update.xpIntoLevelandupdate.xpForNextLevel: useful for progress bars.update.newlyUnlockedTierIDs: tiers unlocked by the latest event.update.didGrantXP: whether the event changed XP.
Use PKConfig when you want to customize level size, XP rewards, tier unlock order, and the mastery requirement for unlocking the next tier:
let config = PKConfig(
levelXP: 120,
masteryXP: 15,
tierOrder: ["beginner", "intermediate", "advanced"],
masteryRequirement: 4
)Apply the same event with your custom config:
let configuredUpdate = PKEngine.apply(
event: event,
to: profile,
config: config
)In practice:
- Persist
configuredUpdate.profile(your newPKProfile) after each event. - Read other
PKUpdatevalues to update your UI (XP gain, level changes, unlock state, and mastery).
This example shows a simple integration pattern: apply progression events, keep the latest PKUpdate, and render a progress bar from the returned values.
progressionkit-demo.mp4
import ProgressionKit
import SwiftUI
struct ProgressionDemoView: View {
@State private var profile = PKProfile()
@State private var lessonNumber = 1
private let config = PKConfig()
private var progress: Double {
min(Double(profile.totalXP) / Double(config.levelXP), 1)
}
var body: some View {
VStack(spacing: 16) {
Text(progress < 1 ? "Level 1" : "Level 2 π₯³")
.font(.headline)
GeometryReader { geometry in
let totalWidth = geometry.size.width
let fillWidth = totalWidth * progress
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 10)
.fill(.gray.opacity(0.25))
RoundedRectangle(cornerRadius: 10)
.fill(.green)
.frame(width: fillWidth)
.animation(.snappy, value: progress)
}
}
.frame(height: 16)
Text("\(Int(progress * 100))%")
.font(.caption)
.foregroundStyle(.secondary)
Button("Complete Lesson") {
let event = PKEvent(
contentID: "lesson.greetings.\(lessonNumber)",
trackID: "japanese-basics",
tierID: "beginner",
wasSuccessful: true
)
let update = PKEngine.apply(
event: event,
to: profile,
config: config
)
withAnimation(.snappy) {
profile = update.profile
}
lessonNumber += 1
}
}
.padding()
}
}
// MARK: - Preview
#Preview {
ProgressionDemoView()
}Use Xcode's built-in support for SPM.
or...
In your Package.swift, add ProgressionKit as a dependency:
dependencies: [
.package(
url: "https://github.com/thatfactory/progressionkit",
from: "0.1.0"
)
]Associate the dependency with your target:
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(
name: "ProgressionKit",
package: "progressionkit"
)
]
)
]Run: swift build