{"id":50740132,"url":"https://github.com/thatfactory/progressionkit","last_synced_at":"2026-06-10T16:32:08.406Z","repository":{"id":358745866,"uuid":"1203733786","full_name":"thatfactory/progressionkit","owner":"thatfactory","description":"A reusable progression engine that turns player performance into configurable XP, levels, and unlocks across games and apps. 📈","archived":false,"fork":false,"pushed_at":"2026-05-18T21:46:20.000Z","size":21,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T23:57:22.074Z","etag":null,"topics":["domain-logic","game-development","gamification","level","leveling","package","progression","swift","swiftpm","ui-agnostic","unlocks","xp"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thatfactory.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-07T10:19:41.000Z","updated_at":"2026-05-18T21:45:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thatfactory/progressionkit","commit_stats":null,"previous_names":["thatfactory/progressionkit"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/thatfactory/progressionkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatfactory%2Fprogressionkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatfactory%2Fprogressionkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatfactory%2Fprogressionkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatfactory%2Fprogressionkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thatfactory","download_url":"https://codeload.github.com/thatfactory/progressionkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thatfactory%2Fprogressionkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34161283,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["domain-logic","game-development","gamification","level","leveling","package","progression","swift","swiftpm","ui-agnostic","unlocks","xp"],"created_at":"2026-06-10T16:32:04.395Z","updated_at":"2026-06-10T16:32:08.395Z","avatar_url":"https://github.com/thatfactory.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://developer.apple.com/swift/\"\u003e\u003cimg alt=\"Swift\" src=\"https://img.shields.io/badge/Swift-6.3-ea7a50.svg?logo=swift\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://developer.apple.com/xcode/\"\u003e\u003cimg alt=\"Xcode\" src=\"https://img.shields.io/badge/Xcode-26.4-50ace8.svg?logo=xcode\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://developer.apple.com/documentation/xcode/swift-packages\"\u003e\u003cimg alt=\"SPM\" src=\"https://img.shields.io/badge/SPM-ready-b68f6a.svg?logo=gitlfs\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://en.wikipedia.org/wiki/List_of_Apple_operating_systems\"\u003e\u003cimg alt=\"Platforms\" src=\"https://img.shields.io/badge/Platforms-iOS%2026+%20%7C%20macOS%2026+%20%7C%20tvOS%2026+%20%7C%20watchOS%2026+%20%7C%20visionOS%2026+-lightgrey.svg?logo=apple\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://thatfactory.github.io/progressionkit/documentation/progressionkit/\"\u003e\u003cimg alt=\"DocC\" src=\"https://img.shields.io/badge/DocC-documentation-0288D1.svg?logo=bookstack\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://en.wikipedia.org/wiki/MIT_License\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/badge/License-MIT-67ac5b.svg?logo=googledocs\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/thatfactory/progressionkit/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/thatfactory/progressionkit/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/thatfactory/progressionkit/actions/workflows/release.yml\"\u003e\u003cimg alt=\"Release\" src=\"https://github.com/thatfactory/progressionkit/actions/workflows/release.yml/badge.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# ProgressionKit\nA reusable progression engine that turns player performance into configurable XP, levels, and unlocks across games and apps. 📈\n\n`ProgressionKit` is a pure Swift package for apps and games that need deterministic progression logic without coupling progression rules to storage or UI frameworks.\n\nIt models:\n\n- `XP` gain from successful performance.\n- Player levels derived from total XP.\n- Track-scoped mastery across distinct content.\n- Tier unlocks such as `beginner`, `intermediate`, and `advanced`.\n\nThe package is deliberately content-agnostic. Host apps decide what a track, content item, and tier mean, then feed those identifiers into `ProgressionKit`.\n\n## Implemented APIs\n\n- `PKEngine`: applies a progression event to a profile and returns the updated profile plus derived progress values.\n- `PKProfile`: persisted progression state for a player.\n- `PKConfig`: tunable progression rules such as level size, XP reward, tier order, and unlock thresholds.\n- `PKEvent`: a single outcome emitted by the host app.\n- `PKUpdate`: the result of applying one event.\n\n## Structure\n\n```mermaid\nflowchart TB\n  subgraph HOST[\"Host App/Game\"]\n    EVENTS[\"Performance Events\"]\n    STORAGE[\"Storage Layer\"]\n    UI[\"UI / HUD / XP Bar\"]\n  end\n\n  subgraph PK[\" \"]\n    ENGINE[\"ProgressionKit\"]\n    PROFILE[\"PKProfile\"]\n    CONFIG[\"PKConfig\"]\n    UPDATE[\"PKUpdate\"]\n  end\n\n  EVENTS --\u003e ENGINE\n  CONFIG --\u003e ENGINE\n  ENGINE --\u003e PROFILE\n  ENGINE --\u003e UPDATE\n  PROFILE --\u003e STORAGE\n  UPDATE --\u003e UI\n```\n\n## Quick Start\n\nImport the package and create an initial player profile:\n\n```swift\nimport ProgressionKit\n\nlet profile = PKProfile()\n```\n\nCreate an event whenever the player finishes one unit of content:\n\n```swift\nlet event = PKEvent(\n    contentID: \"lesson.greetings.001\",\n    trackID: \"japanese-basics\",\n    tierID: \"beginner\",\n    wasSuccessful: true\n)\n```\n\nApply the event to the profile:\n\n```swift\nlet update = PKEngine.apply(\n    event: event,\n    to: profile\n)\n```\n\n`update` is a `PKUpdate` value that contains the updated `PKProfile` and derived progression values your app can render immediately.\n\nCommon `PKUpdate` values you will typically use:\n\n- `update.profile`: persist this as the new `PKProfile`.\n- `update.playerLevel`: current player level.\n- `update.xpIntoLevel` and `update.xpForNextLevel`: useful for progress bars.\n- `update.newlyUnlockedTierIDs`: tiers unlocked by the latest event.\n- `update.didGrantXP`: whether the event changed XP.\n\n## Configure Progression Rules\n\nUse `PKConfig` when you want to customize level size, XP rewards, tier unlock order, and the mastery requirement for unlocking the next tier:\n\n```swift\nlet config = PKConfig(\n    levelXP: 120,\n    masteryXP: 15,\n    tierOrder: [\"beginner\", \"intermediate\", \"advanced\"],\n    masteryRequirement: 4\n)\n```\n\nApply the same event with your custom config:\n\n```swift\nlet configuredUpdate = PKEngine.apply(\n    event: event,\n    to: profile,\n    config: config\n)\n```\n\nIn practice:\n\n- Persist `configuredUpdate.profile` (your new `PKProfile`) after each event.\n- Read other `PKUpdate` values to update your UI (XP gain, level changes, unlock state, and mastery).\n\n## SwiftUI Example (Simple Progress Bar)\n\nThis example shows a simple integration pattern: apply progression events, keep the latest `PKUpdate`, and render a progress bar from the returned values.\n\n### Video\n\nhttps://github.com/user-attachments/assets/3920bbde-7b6b-40f6-b02f-f5506410b4fb\n\n### Code\n\n```swift\nimport ProgressionKit\nimport SwiftUI\n\nstruct ProgressionDemoView: View {\n    @State private var profile = PKProfile()\n    @State private var lessonNumber = 1\n\n    private let config = PKConfig()\n\n    private var progress: Double {\n        min(Double(profile.totalXP) / Double(config.levelXP), 1)\n    }\n\n    var body: some View {\n        VStack(spacing: 16) {\n            Text(progress \u003c 1 ? \"Level 1\" : \"Level 2 🥳\")\n                .font(.headline)\n\n            GeometryReader { geometry in\n                let totalWidth = geometry.size.width\n                let fillWidth = totalWidth * progress\n\n                ZStack(alignment: .leading) {\n                    RoundedRectangle(cornerRadius: 10)\n                        .fill(.gray.opacity(0.25))\n\n                    RoundedRectangle(cornerRadius: 10)\n                        .fill(.green)\n                        .frame(width: fillWidth)\n                        .animation(.snappy, value: progress)\n                }\n            }\n            .frame(height: 16)\n\n            Text(\"\\(Int(progress * 100))%\")\n                .font(.caption)\n                .foregroundStyle(.secondary)\n\n            Button(\"Complete Lesson\") {\n                let event = PKEvent(\n                    contentID: \"lesson.greetings.\\(lessonNumber)\",\n                    trackID: \"japanese-basics\",\n                    tierID: \"beginner\",\n                    wasSuccessful: true\n                )\n\n                let update = PKEngine.apply(\n                    event: event,\n                    to: profile,\n                    config: config\n                )\n\n                withAnimation(.snappy) {\n                    profile = update.profile\n                }\n                lessonNumber += 1\n            }\n        }\n        .padding()\n    }\n}\n\n// MARK: - Preview\n\n#Preview {\n    ProgressionDemoView()\n}\n```\n\n## Integration\n\n### Xcode\nUse Xcode's [built-in support for SPM](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app).\n\n*or...*\n\n### Package.swift\nIn your `Package.swift`, add `ProgressionKit` as a dependency:\n\n```swift\ndependencies: [\n    .package(\n        url: \"https://github.com/thatfactory/progressionkit\",\n        from: \"0.1.0\"\n    )\n]\n```\n\nAssociate the dependency with your target:\n\n```swift\ntargets: [\n    .target(\n        name: \"YourTarget\",\n        dependencies: [\n            .product(\n                name: \"ProgressionKit\",\n                package: \"progressionkit\"\n            )\n        ]\n    )\n]\n```\n\nRun: `swift build`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthatfactory%2Fprogressionkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthatfactory%2Fprogressionkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthatfactory%2Fprogressionkit/lists"}