This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Provenance is a multi-platform emulator frontend for iOS/tvOS supporting 60+ retro gaming systems. Written primarily in Swift with Objective-C/C++ bridge layers for emulator cores.
-
Xcode 16.x or Xcode 26+ (use
xcode-select -pto confirm active version; both are supported) -
Ruby + Bundler (for fastlane)
-
make setupto install all dependencies -
Mininimum targets: iOS 17+, tvOS 17+ mandatory, Linux, macOS, VisionOS, watchOS equivlant release versions when applicable
Copy CodeSigning.xcconfig.sample to CodeSigning.xcconfig and fill in your developer account details.
make open # Open Provenance.xcworkspace in Xcode
make ios # Update submodules + build iOS
make tvos # Update submodules + build tvOS
make update # Pull + update submodules + install gems
make test # Run tests via fastlaneBuild from Xcode: open Provenance.xcworkspace and select a scheme. Start with Provenance-Lite (fastest build) before moving to Provenance-Release or Provenance-XL (Release).
Note: Initial builds may fail because some source files are generated lazily at compile time. Retry if Xcode gets the build order wrong on first build.
- Provenance-Lite (AppStore) — lightweight, fewer cores
- Provenance (AppStore) — standard release
- Provenance-XL (Release) — includes more RetroArch and native cores, not really used but should be kept updated regardless
- Each is a multi-platfor target for iOS, tvOS and macOS Catalyst, and macOS where available. iOS and tvOS are our primary focus with possible future other Apple platform support
GitHub Actions (.github/workflows/build.yml) builds all target variants on push/PR to develop and master.
The app is split into ~26 PV* Swift Package frameworks. Key modules:
- PVAppIntents — Siri and App intents
- PVAudio — Swift audio apis
- PVCheevos — RetroAchievements API integration
- PVCoreAudio / PVAudio — Audio engine and playback
- PVCoreBridge — Protocol/bridge between app and emulator cores
- PVCoreBridgeRetro — RetroArch-specific core bridge
- PVCoreLoader — Dynamic loading of emulator core packages
- PVEmulatorCore — Base classes for emulator implementations
- PVFeatureFlags — Feature flags manager
- PVHashing — ROM file hashing for identification
- PVHelp — SwiftUI wiki and blog parser
- PVJIT — JIT compilation support for emulator cores
- PVLibrary — Data models, Realm persistence, game database, CloudKit sync
- PVLogging — Logging infrastructure (CocoaLumberjack-based)
- PVLookup — Game metadata and artwork and cheats lookup
- PVNetplay — Central netplay support code for retroarch and game center
- PVPLlists — Core and System plist processing and other serializer
- PVPatching — Support for patch files for roms (WIP)
- PVPrimitives — Base data types shared across modules
- PVQuicklookSupport — iOS and macOS Quicklook api support code
- PVRcheevos — RetroAchievements C client integration
- PVSettings — User preferences
- PVShaders — Metal shdader manager support
- PVSupport — Shared utilities
- PVThemes — UI Theming Support
- PVUI — SwiftUI-based shared UI components
- PVWebServer — Swift/Objective-C SwiftPM module for GCDWebServer and WIP new Swift webserver for webdav and http file management, future REST API
Each core lives in Cores/<CoreName>/ and typically contains:
- A git submodule with the upstream emulator source
- An Xcode project (
PV<Core>.xcodeproj) and/orPackage.swift - A bridge layer (
PV<Core>Core/) withPV<Core>CoreBridge+Controls.mm(Objective-C++) connecting the emulator toPVCoreBridge
Cores depend on PVEmulatorCore, PVCoreBridge, PVSupport, PVObjCUtils, and PVLogging via relative SPM paths.
RetroArch-based cores live in CoresRetro/RetroArch/ and use PVCoreBridgeRetro.
- Provenance/ — Main iOS app
- ProvenanceTV/ — tvOS-specific app target
- Extensions/ — Spotlight indexing, TopShelf (tvOS)
- Realm for local metadata, game library, and CloudKit record IDs
- CloudKit for syncing ROMs, save states, and BIOS files across devices
- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access (see DEVELOPER.md for patterns)
- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs
- Emulator cores are loaded as dynamic packages at runtime via
PVCoreLoader - Controller input mapping is handled per-core in
*Bridge+Controls.mmfiles
SwiftLint (.swiftlint.yml):
- Line length: 200 chars
- Only lints:
Provenance,ProvenanceTV,PVLibrary,PVSupport,TopShelf,Spotlight - Excludes:
Cores,Carthage,Scripts,fastlane,.build force_castandforce_tryare warnings (not errors)
SwiftFormat (.swiftformat):
- 4-space indent, no indent for
case - Excludes:
Carthage,Cores
- The
developbranch is the main development branch - Emulator core submodules are in
Cores/<name>/<upstream-submodule-dir>— avoid modifying third-party upstream source directly (exception: the RetroArch fork atCoresRetro/RetroArch/RetroArch/is maintained in-repo for Provenance) - Each PV* module is a standalone Swift Package with its own
Package.swift - The top-level
Package.swiftis minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace - Build variants (Lite/Standard/XL) differ in which cores are included; see
CoresRetro/RetroArch/Scripts/for core lists per target
# Lint changed Swift files
swiftlint lint --path <file>
# Build a standalone SPM module (Tier 0-2 only)
cd PV<Module> && swift build
# Test a standalone SPM module
cd PV<Module> && swift test
# Xcode simulator build (full app, slow)
xcodebuild build -workspace Provenance.xcworkspace \
-scheme "Provenance-Lite (AppStore)" \
-destination "generic/platform=iOS Simulator" \
CODE_SIGNING_ALLOWED=NO | xcprettyModules are organized by dependency depth. Agents should scope changes to the lowest tier possible.
| Tier | Modules | Can swift build standalone? |
|---|---|---|
| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |
| 1 | PVLogging, PVPlists, PVHashing | Yes |
| 2 | PVSettings, PVPrimitives | Yes |
| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |
| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode |
| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLookup, PVLibrary | Needs Xcode |
| 6 | PVUI, App targets | Full workspace build |
Each core has a PV<Core>CoreBridge+Controls.mm file that maps controller input:
- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {
// Map GCController buttons to emulator-specific button constants
// Use PVCoreBridge protocol methods to forward input
}When modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).
- GitHub workflow files —
.github/workflows/*.ymlmay be edited when fixing CI issues or optimizing builds. However, GitHub Actions bot PRs cannot push workflow changes (requiresworkflowspermission) — if an agent PR needs to include workflow changes, note this in the PR description so a maintainer can push them manually. - Submodule source —
Cores/<name>/<upstream-dir>/contents are third-party upstream code (do not casually fork in place) - Generated files —
Version.h,Version.swift, files incmake/build dirs - CodeSigning.xcconfig — contains developer-specific credentials
- project.pbxproj — editing is permitted and sometimes required (e.g., adding new app targets). When you add a new target, use deterministic UUID prefixes (e.g.
C0C0CAFE...) to make additions easy to identify. UsePBXFileSystemSynchronizedRootGroupfor source directories (Xcode 16+). Prefer minimal diffs — only touch the sections that need changing. - RetroArch fork —
CoresRetro/RetroArch/RetroArch/is a Provenance-maintained submodule; changes for build integration or features are allowed with focused diffs
Provenance targets iOS 17+, tvOS 17+, macOS 14+ (Catalyst), visionOS 1+. All new code MUST be written against these minimum versions — do not add availability guards or fallbacks for APIs available since iOS 17 or earlier.
Prefer modern Swift/SwiftUI APIs when the minimum deployment target supports them:
| Prefer (iOS 16+/17+) | Over (older) |
|---|---|
ShareLink |
UIActivityViewController wrapped in UIViewControllerRepresentable |
@Observable macro (iOS 17+) |
@ObservableObject + @Published |
NavigationStack |
NavigationView |
.navigationBarTitleDisplayMode(.inline) on non-tvOS |
conditional guard |
UIWindowScene.keyWindow |
UIApplication.shared.keyWindow (deprecated iOS 13) |
@StateObject vs @ObservedObject rule:
@StateObject— use only when the view creates and owns the object's lifetime (new instances).@ObservedObject— use for singletons (e.g.Foo.shared) and objects passed in from outside. Using@StateObjectwith a singleton is semantically wrong even though it compiles.
Agents MUST run these checks before creating a PR. Do NOT skip any step.
-
Compile check — Every changed file must compile. For Tier 0-2 modules:
cd PV<Module> && swift build. For higher tiers or ObjC files: verify syntax is correct (no undefined symbols, no mismatched types, no missing imports). -
Lint — Run
swiftlint lint --path <file>on every changed Swift file. -
Unit tests — If the module has tests, run them:
cd PV<Module> && swift test. If adding new logic, add test coverage. -
No dead code — Don't add unused properties, unused imports, or commented-out code blocks. If Copilot flags dead code, that means you should have caught it.
-
No magic numbers — Extract constants. Don't hardcode values that are used in multiple places.
-
Use
SystemIdentifierenum, not raw strings — Never compare system identifiers using raw string literals like"com.provenance.n64". Use theSystemIdentifierenum fromPVPrimitives:// ❌ WRONG — fragile, typo-prone, no compile-time safety if self.systemIdentifier == "com.provenance.n64" { ... } if self.systemIdentifier?.contains("atarist") == true { ... } // ✅ CORRECT — type-safe, refactor-safe if SystemIdentifier(rawValue: self.systemIdentifier ?? "") == .N64 { ... } // ✅ CORRECT — check multiple systems let sysID = SystemIdentifier(rawValue: self.systemIdentifier ?? "") if sysID == .SNES || sysID == .NES { ... } // ✅ CORRECT — switch switch SystemIdentifier(rawValue: self.systemIdentifier ?? "") { case .AtariST: setupHatari() case .DOS, .DOOM: setupDOSBox() default: break }
In Objective-C there is no enum, so use the
SystemIdentifierraw string constants directly viaPVSystemor define a localNSString * const— do NOT embed thecom.provenance.*string inline more than once:// ❌ WRONG if ([self.systemIdentifier containsString:@"com.provenance.atarist"]) { ... } // ✅ CORRECT — define a constant or use the existing PVSystem identifier static NSString * const PVAtariSTSystemIdentifier = @"com.provenance.atarist"; if ([self.systemIdentifier isEqualToString:PVAtariSTSystemIdentifier]) { ... }
The
SystemIdentifierenum is inPVPrimitives/Sources/PVSystems/SystemIdentifier.swift. ImportPVPrimitivesto use it.systemIdentifieron the core (PVEmulatorCore) is aString?. -
Type safety — Check that optional unwrapping is correct (no double-optionals from
?.chains). Check that enum cases exist before referencing them. Check that function signatures match call sites. -
Thread safety — If reading a property from a background queue that's written from main, snapshot it into a local
letfirst. Don't use@Publishedproperties across threads without synchronization. -
Multi-platform compilation — Provenance builds for iOS, tvOS, macOS (Catalyst), and visionOS. All new code MUST compile on all platforms. Agents must mentally verify every changed file compiles for at least iOS AND tvOS before creating a PR.
Platform guard patterns:
#if os(iOS)/#if os(tvOS)— OS-specific code#if !os(tvOS)— iOS/macOS features unavailable on tvOS (e.g.,DragGesture,UIImpactFeedbackGenerator,UIDevice.current.orientation)#if canImport(UIKit)— UIKit vs AppKit#if targetEnvironment(simulator)— simulator-only code#if targetEnvironment(macCatalyst)— Mac Catalyst specifics
Common tvOS pitfalls (these WILL fail to compile on tvOS):
DragGesture— unavailable on tvOSUIImpactFeedbackGenerator/ haptics — iOS onlyUIDevice.current.orientation— no orientation on tvOS.onHover— not available on tvOSNavigationSplitViewdetails differ on tvOS
Linux support — Non-UI SPM modules (Tier 0-2: PVHashing, PVLookup, PVPlists, etc.) should compile and test on Linux. Use
#if canImport(Foundation)not#if canImport(UIKit)for cross-platform code. CI runsswift teston Debian runners for these modules.
- Target the
developbranch - Include unit tests for new logic — this is not optional. If you add a new class, manager, or utility, add tests. Use
swift testfor SPM modules. - Keep scope focused — one logical change per PR
- Run
swiftlinton changed files before submitting - Agent PRs should use the
[Agent]prefix in title - All Pre-PR Validation steps above must pass before creating the PR
- Branches:
agent/issue-<N>for agent work,feature/<description>for features - Commits: Use conventional commits (
fix:,feat:,chore:,build:,refactor:,test:,docs:) - Keep commit messages concise (< 72 chars for subject line)