{"owner":"Provenance-Emu","repo":"Provenance","hasSkills":true,"totalSkillsCount":7,"totalTokensCount":18561,"categories":["cursor-rule","claude-rule","copilot-instructions","root-instruction","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"Provenance":{"command":"npx","args":["-y","@modelcontextprotocol/server-Provenance"]}}},"found":[".cursorrules","CLAUDE.md",".github/copilot-instructions.md","AGENTS.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{".cursorrules":"You are working on Provenance, a multi-platform emulator frontend for iOS/tvOS.\n\n## Key Rules\n- Read CLAUDE.md for full project conventions, module tiers, and architecture\n- Target the `develop` branch for all PRs\n- Use conventional commits: fix:, feat:, chore:, build:, refactor:, test:, docs:\n- Do NOT modify third-party submodule source in `Cores/<name>/<upstream-dir>/`\n- Do NOT modify project.pbxproj unless absolutely necessary\n- Do NOT modify CodeSigning.xcconfig\n- Prefer SPM Package.swift changes over Xcode project changes\n- Keep changes focused — one logical change per PR\n\n## Swift Conventions\n- 4-space indent, no indent for case in switch\n- Line length: 200 chars max\n- Prefer async/await over Combine for new code\n- Use guard let / if let, avoid force unwraps in production code\n- Realm objects are thread-confined — pass Object IDs across threads\n\n## Build\n- Open Provenance.xcworkspace (not .xcodeproj)\n- Start with Provenance-Lite scheme for fastest builds\n- Initial builds may fail due to lazy code generation — retry once\n- Never build iOS/tvOS from command line unless explicitly asked\n\n## Module Tiers (lowest = fewest dependencies)\n- Tier 0: PVObjCUtils, PVFeatureFlags, PVCheevos\n- Tier 1: PVLogging, PVPlists, PVHashing\n- Tier 2: PVSettings, PVPrimitives\n- Tier 3+: PVSupport, PVAudio, PVCoreBridge, etc.\n- Scope changes to the lowest tier possible\n\n## Testing\n- swift test in standalone SPM modules (Tier 0-2)\n- Xcode workspace build for higher-tier modules\n- Run swiftlint on changed files before submitting\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.x or Xcode 26+ (use `xcode-select -p` to confirm active version; both are supported)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n- Minimum targets: iOS 17+, tvOS 17+ mandatory. macOS today = \"Designed for iPad\" (the iOS binary on Apple Silicon); a native macOS target is planned (see docs/superpowers/specs/2026-08-07-macos-visionos-strategy-design.md). Mac Catalyst is NOT supported. visionOS/watchOS/Linux: SPM packages declare them — keep code compiling with guards, but no app ships for them.\n\n### Code Signing\nCopy `CodeSigning.xcconfig.sample` to `CodeSigning.xcconfig` and fill in your developer account details.\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\n**Azahar native artifact preparation:** before opening or resolving the shipping workspace, run\n`make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim` for the intended destination).\n`make open` prepares iOS by default. The command reuses the native slice cache and packs its XCFramework;\nSwiftPM owns the wrapper, bridge, Core.plist and migrator test targets. Tuist's `make dev-azahar` performs\nthe same preparation through `dev-generate`. Native engine changes require preparing again before building.\nArm64 simulator builds require `ARCHS=arm64` or `EXCLUDED_ARCHS=x86_64` at the build invocation level.\n\n**Dolphin native artifact preparation:** run `make prepare-dolphin DOLPHIN_SLICE=ios`\n(or `tvos` for a tvOS device) before resolving the shipping workspace. `make open`\nprepares both Azahar and Dolphin. Shipping and Tuist share `PVDolphin-Dynamic`;\nSwiftPM owns the bridge, wrapper, Core.plist, Config and Sys resource bundles.\n`make dev-dolphin` prepares its selected slice before generating Tuist.\n\n**PPSSPP:** shipping and Tuist share `PVPPSSPP-Dynamic`. SwiftPM compiles the native\nengine, its dependencies and the Swift/ObjC++ wrapper, and owns Core.plist and PSP\nassets. No CMake/Xcode project preparation is needed. `make dev-ppsspp` opts into\nthe native focused iOS device app; Thin retains the independent libretro core.\nThe checked-in FFmpeg XCFramework includes arm64 device and simulator slices for\niOS and tvOS. Rebuild it with `Scripts/cores/build_ppsspp_ffmpeg.sh`; the legacy\ncodec headers stay private to PPSSPP.\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores, not really used but should be kept updated regardless\n- Each is a multi-platform 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\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n- **CI cache can shadow source changes.** `actions/cache@v4` caches `Cores/`, `CoresRetro/`, `Dependencies/`, `PVRcheevos/` for submodule speed. A post-restore step runs `git checkout HEAD -- Cores CoresRetro Dependencies PVRcheevos` to un-shadow parent-repo-tracked files. If a CI build ignores your source changes, check that this step ran successfully.\n\n- **xcbeautify hides Run Script output — don't debug CI from the console log.** The build pipes through `2>&1 | tee /tmp/xcodebuild.log | xcbeautify`, and xcbeautify filters out Run Script phase output. `GetModule:` / `MakeFrameworks:` lines therefore never appear in `gh run view` or `gh api .../jobs/<id>/logs`, even when the script ran fine — an empty grep means \"filtered\", not \"never ran\". Those lines exist only in the raw log, which is uploaded on failure as artifact `xcodebuild-log-<IPA_NAME>` (3-day retention):\n\n  ```bash\n  gh run download <run-id> -n xcodebuild-log-Provenance-iOS\n  ```\n\n  The failure-report step also prints, in order: `: error:` diagnostics, the `The following build commands failed:` block, and the last 100 raw lines. It greps `\": (fatal )?error:\"` with a **leading colon** on purpose — a bare `\"error:\"` also matches ObjC selectors like `loadStateToFileAtPath:error:` inside `-Wincomplete-implementation` warnings, which floods the report and buries the real errors. Run Script failures (e.g. \"Generate Frameworks\" exiting non-zero) produce no `error:` line at all and surface *only* in the `build commands failed` block.\n\n- **Symbolicating a crash log.** `release.sh` (local and `testflight.yml`) copies each archive's dSYMs to `build/export/<version>-<build>/dSYMs/<ios|tvos>/` and uploads them to Sentry (`provenance-emu/provenance`, needs `SENTRY_AUTH_TOKEN` or `~/.sentryclirc`; a failed upload only warns). Sentry's crash handler is off, so crashes arrive via MetricKit and on-device `.ips` files. To symbolicate by hand, match the `.ips` binary UUID to a dSYM (`dwarfdump --uuid <x>.dSYM`), then `xcrun atos -arch arm64 -o <x>.dSYM/Contents/Resources/DWARF/<name> -l <load address> <address>` (frames in an embedded framework need that framework's dSYM). Prebuilt binaries — libretro buildbot `*.libretro.framework`, ffmpeg, MoltenVK, Sentry, `PVlibDolphin-ios` — ship no dSYM and stay unsymbolicated.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVAppIntents** — Siri and App intents\n- **PVAudio** — Swift audio apis\n- **PVCheevos** — RetroAchievements API integration\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVFeatureFlags** — Feature flags manager\n- **PVHashing** — ROM file hashing for identification\n- **PVHelp** (separate repo, github.com/Provenance-Emu/PVHelp, shared with iCube) — wiki navigation, page fetch/cache and GitBook preprocessing. App-neutral: Provenance's URLs, page paths and offline SUMMARY.md are `ProvenanceWiki` in PVUIBase (`Resources/WikiContent`)\n- **PVJIT** — JIT compilation support for emulator cores\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVLogging** — Logging infrastructure (CocoaLumberjack-based)\n- **PVLookup** (separate repo, github.com/Provenance-Emu/PVLookup, shared with iFly and iCube) — game metadata, artwork and cheat lookup over libretrodb, OpenVGDB, TheGamesDB, ShiraGame and the libretro cheat database. Ships no database bytes (see \"Lookup databases\" below). Change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVLibrarySnapshot** (separate repo, github.com/Provenance-Emu/PVLibrarySnapshot, shared with iCube and iFly) — the App Group snapshot and cover JPEGs the tvOS Top Shelf reads. Provenance's configuration (app group, `provenance://open?md5=` launch URL) is `PVLibrarySnapshotConfig` in PVAppIntents; PVUIBase `LibrarySnapshotWriter` (tvOS only for now) writes it from `WidgetDataWriter.writeFromRealm()`. Widgets and Quick Look still read `PVLibrarySnapshotLegacy` (`widget.*` keys), whose type names collide with the shared package's: no file may import both\n- **PVNetplay** — Central netplay support code for retroarch and game center\n- **PVPLlists** — Core and System plist processing and other serializer\n- **PVPatching** — ROM/disc patches (IPS, IPS32, BPS, UPS, PPF, xdelta3): import, matching, cache, Patch Applier, patch creation, online catalog\n- **PVPatchEngine** — the streaming patch engine PVPatching applies through (bounded memory, 64-bit offsets, vendored Apache-2.0 xdelta3). Its own repo, github.com/Provenance-Emu/PVPatchEngine, which iCube and iFly also depend on by URL: change it there, tag a release, bump the version in `PVPatching/Package.swift`. Only `PVPatching/Sources/PVPatching/Engine/EngineBridge.swift` may import it (its `PatchFormat`/`PatchError`/`PatchProgress` clash with PVPatching's)\n- **PVPrimitives** — Base data types shared across modules\n- **PVSystems** (separate repo, github.com/Provenance-Emu/PVSystems) — `SystemIdentifier` and per-system metadata (TheGamesDB/libretrodb/OpenVGDB IDs, RetroAchievements console IDs, per-game BIOS manifest). Shared with iCube and iFly: change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVQuicklookSupport** — iOS and macOS Quicklook api support code\n- **PVRcheevos** — RetroAchievements C client integration\n- **PVSettings** — User preferences\n- **PVShaders** — video filters: Provenance's hand-written Metal filters, the bundled libretro slang library (runtime in `PVSlangRuntime`), filter catalog/selection store, previews, non-linear stretch\n- **PVSupport** — Shared utilities\n- **PVThemes** — UI Theming Support\n- **PVTouchOverlay** — Programmatic on-screen controller: layout families, live editor, shared touch primitives\n- **PVUI** — SwiftUI-based shared UI components\n- **PVWebServer** — Swift/Objective-C SwiftPM module for GCDWebServer and WIP new Swift webserver for webdav and http file management, future REST API\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n**Core taxonomy — not every `Cores/PV*` directory is an active shipping core.**\n- **Active native PV* cores** (custom forks or long-supported legacy we\n  actively extend): Azahar, Dolphin, FCEU, Genesis-Plus-GX, Mednafen,\n  Mupen64Plus, ProSystem, snes9x, Stella, plus mGBA, PicoDrive and TGBDual\n  for native-only features. Source of truth:\n  `docs/superpowers/specs/2026-10-10-core-audit.md` (KEEP rows). Retired\n  cores live on as `PVCore.retiredCores` entries (`RetiredCoreMigration`,\n  `RetiredBatterySaveMigration`); `Cores/Debug` stays for its `PVDebug.c` simulator stub; no new `Cores/` project without an audit row.\n- **Deprecated native cores** (`PVCore.deprecatedCores`): Atari800, Bliss,\n  CrabEMU, Gambatte and VisualBoyAdvance-M still ship because they shipped in\n  3.3.x and users have save states on them that the libretro dylibs cannot\n  load, but the libretro cores are the recommended choice. Pickers label them \"(Deprecated)\" and sort them last;\n  nothing migrates away from them. Don't extend them.\n- **Pruned (2026-10, `feature/prune-dead-cores`):** 27 `Cores/` projects that\n  duplicated a libretro buildbot dylib or were never wired were deleted:\n  PokeMini, VirtualJaguar, O2EM, Desmume2015, melonDS, BeetlePSX, DosBox, DuckStation,\n  FreeIntv, GameMusicEmu, Gearcoleco, JollyGoodEmulation, Mini_vMac, Mu,\n  Mupen64Plus-NX, Play, Potator, Reicast, Sudachi, VecX, Yabause, fuse, opera,\n  pcsx_rearmed, sm64ex, snesticle, supergrafx. Their games run on the thin\n  wrapper through the replacement table in `PVCore.swift` where a replacement exists (17\n  entries); cores that were disabled or never shipped have no entry.\n- **Rule of thumb:** if a libretro buildbot dylib already serves the\n  core and the thin wrapper handles it, fix the thin wrapper (or the\n  upstream dylib via Provenance fork), not a native PV* shell.\n  The whole point of the thin wrapper is to make those cores feel\n  basically native without per-core PV* maintenance.\n\n**Wrapper platform defaults — patch the right file:**\n- **Both iOS and tvOS** now default to the THIN libretro wrapper\n  (`PVCoreBridgeRetro/Sources/PVLibRetro/PVThinLibretroFrontend.mm`\n  + `PVThinLibretroCore*.swift`). dlopen + function pointers against\n  the libretro buildbot dylibs; no full RetroArch runtime.\n- **PPSSPP runs on the THIN wrapper, on Vulkan** (no force-route since `2f09fd7802`, 2026-05-28). On GLES, PPSSPP spawns its own render thread (`useEmuThread = GPUCORE_GLES`) that never has a current EAGL context, so GL can't work on thin; `GET_PREFERRED_HW_RENDER` returns Vulkan for PSP and GL is rejected (`e66b2a28a4`, `79a091c4b2`), with `context_reset` deferred until after `retro_load_game`. A failed deferred Vulkan setup now fails the load, and fence waits are bounded (`f87b9d17e1`).\n- **Thin `rendersToOpenGL` is true for every HW-render core, Vulkan included.** `PVThinLibretroFrontend` returns `_hwRenderRequested`, so a Vulkan core takes the \"OpenGL\" branch of `PVMetalViewController.draw(in:)` and never reaches the software-upload path. `rendersToVulkan` is the one that distinguishes them.\n- **Thin core options are replayed at boot, not read on demand.** The frontend holds option values only in memory; `PVThinLibretroCore.applyPersistedCoreOptions()` pushes the saved ones in from `afterCoreInitBlock` (after `retro_init`, before `retro_load_game`). The UI stores a switch state or a choice *label*; `ThinCoreOptionDefinition.rawValue(forStored:)` maps it to the core's value. A replay that never finishes booting is skipped once on the next launch.\n- The thick RetroArch wrapper (full in-process RetroArch, `PVRetroArch.framework`) has been removed; every libretro core runs on thin. What remains under `CoresRetro/RetroArch/` serves thin: `Core.plist` (the libretro core list, bundled via the `PVCoreLoader` resource symlink `RetroArchCore.plist`), `scripts/` (dylib download/packaging, `cores.yml`), `modules/` and `libretro-super`. A core from that list is available (`PVCore.hasCoreClass`) only when its `<name>.libretro.framework` is in the app bundle. Its top-level `com.provenance.core.retroarch` entry (the old launcher) is `PVDisabled`; its `PVRetroArch.PVRetroArchCoreCore` principleClass strings stay because `PVCoreFactory` keys on them.\n- **Thin netplay is libretro netpacket (env 78) only** — cores that register it (gpSP, melonDS DS) get netplay; RetroArch rollback netplay went with the RetroArch runtime. `NetpacketTransport` (PVNetplay) carries packets over framed TCP; `PVThinLibretroFrontend` hands every netpacket callback to the core on the emulation thread from a per-frame pump before `retro_run`, never from the network queue. Hosts advertise Bonjour `_provenance-np._tcp`, which must stay listed in every app Info.plist's `NSBonjourServices`. Cores with their own netplay socket (Dolphin ENet, mGBA, Mednafen, PPSSPP ad hoc) set `netplayAdvertisesOwnRoom = false` (the default) and `PVNetplayManager.host(settings:game:)` publishes the room for them (`NetServiceRoomAdvertiser`); TXT keys live in `NetplayRoomTXTRecord`. Without that, a LAN room is invisible to Browse.\n- **Thin wrapper gaps:** deferred Vulkan context setup (GL is now eagerly activated; Vulkan still deferred). BIOS files synced from `BIOSPath` into system directory before `retro_load_game`. `ScalingMode` integration works (via `PVMetalViewController` + DeltaSkin skin container). Fast-forward wired (`setGameSpeed:` override syncs `_speedMultiplier` + `_audioPaused`).\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access (see DEVELOPER.md for patterns)\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying third-party upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n### Build & toolchain gotchas\n\n- **Most workspace `.xcodeproj`s list sources explicitly** (e.g. the remaining native core projects), so a new file compiles in SwiftPM yet breaks the archive build. `Scripts/audits/check_pbxproj_sources.py` (CI: `xcode-project-sources.yml`) flags a source file sitting beside compiled ones that its project doesn't reference.\n- **Every script under `Scripts/` or `CoresRetro/RetroArch/scripts/` must be in `Scripts/maint/jobs.toml`** (as a job, or under `[ignore]`); `maint.yml` fails PRs that add an unregistered one. `make maint` (dashboard) / `python3 Scripts/maint/maint.py status` show which generated files are stale. Scripts that find the repo root from their own path sit two levels deep (`Scripts/<category>/`).\n- **PVUI cannot build inside a git worktree.** The PackageBuildInfo plugin reads `.git/HEAD`; a worktree's `.git` is a file. Run PVUI tests from an rsync'd copy with a fake `.git/HEAD` and submodule symlinks (see the Phase 1 overlay plan, Global Constraints).\n- **`gh issue list` has no `--sort` flag.** Use `gh issue list --search \"sort:created-desc\"` or `gh issue list --json number,title,createdAt --jq '.'` for sorted/filtered queries.\n\n### Dev workspace (Tuist)\n\nA second, generated workspace for fast iteration. `Provenance.xcodeproj` still ships\n(CI, fastlane, release.sh). Spec: `docs/superpowers/specs/2026-10-10-dev-workspace-design.md`.\n\n- **Generate / open:** Tuist 4.200.0 is pinned in `.mise.toml`. Run `mise exec -- tuist generate --no-open`\n  (or `make dev-generate`) from the repo root; `make dev` also opens `Provenance-Dev.xcworkspace`.\n  Generated files (`Provenance-Dev.xcworkspace`, `Dev/Provenance.xcodeproj`, `Dev/Derived`) are\n  gitignored. Never commit them. The Tuist project is named `Provenance` and lives in `Dev/`\n  because `Build.xcconfig` derives bundle ids, the app group and the iCloud container from\n  `$(PROJECT_NAME:lower)`.\n- **Focused apps:**\n  - `Provenance-Dev-UI`: mGBA, Stella, snes9x.\n  - `Provenance-Dev-Thin`: no native cores; 4 libretro dylibs (mednafen_psx_hw, mupen64plus_next, snes9x, ppsspp).\n  - `Provenance-Dev-Azahar`: Azahar.\n- `Provenance-Dev-EmuThree` (opt-in, iOS device): `make dev-emuthree` uses the same\n  `PVEmuThree-Dynamic` SwiftPM product as shipping. The package compiles the legacy engine\n  and bridge sources and selects the arm64 iOS/tvOS device or simulator vendor slice;\n  no core Xcode project is needed. Rebuild those slices with\n  `python3 Scripts/cores/build_emuthree_vendors.py`.\n  - `Provenance-Dev-Dolphin` (opt-in, device only): `Cores/Dolphin/Package.swift` wraps\n    `PVlibDolphin.xcframework` as a `.binaryTarget`. SwiftPM will not load the package until\n    `build_slice.py dolphin <slice>` has produced the xcframework, so `make dev-dolphin` runs it first\n    and sets `TUIST_DEV_DOLPHIN=1`; without that variable the app and package are not in the generated\n    project (CI never sets it). The ObjC++ target reaches the Swift module through the runtime\n    (`#if SWIFT_PACKAGE` in `PVDolphinCore.mm`), because SwiftPM can't build the mixed target as one.\n\n  Build with `make dev-ui|dev-azahar|dev-thin` (derived data in `build/dev-dd`; override with\n  `DEV_DERIVED=`). Each app is one iOS+tvOS target with bundle id\n  `org.provenance-emu.provenance.dev.<slug>`.\n- **Simulator builds are ad-hoc signed by `Dev/Config/Dev.xcconfig`.** Never pass\n  `CODE_SIGNING_ALLOWED=NO` to a dev build: the app then dies in dyld on the unsigned embedded\n  MoltenVK.\n- **Dev apps are arm64-only; Intel is unsupported** (decided 2026-10-12: no Intel Mac runs a\n  supported Xcode). `DevSettings` sets `EXCLUDED_ARCHS[sdk=*simulator*] = x86_64` for every focused\n  app, and the Makefile, harness and both CI workflows also pass `EXCLUDED_ARCHS=x86_64` to\n  xcodebuild, because a project setting never reaches package targets (CI kept compiling x86_64\n  for every package without it). Prebuilt cores need no x86_64 slice and SwiftPM core targets need\n  no per-arch source lists. Don't add x86_64 workarounds (stubs, generic-source fallbacks) here.\n- **The dev workspace has no committed Package.resolved, so it resolves the newest remote\n  packages.** Anything that only works because `Provenance.xcworkspace`'s resolved file pins an\n  older version breaks here first. Known case: Hummingbird imports `ServiceContextModule` without\n  declaring it, and swift-service-context 1.4.0 / swift-distributed-tracing 1.6.0 (2026-10-12)\n  dropped it from `Tracing`'s dependency chain, so builds failed at random with \"no such module\n  'ServiceContextModule'\"; `PVWebServer/Package.swift` caps both packages. Fix such breaks with a\n  version cap in the declaring package, not with xcodebuild flags.\n- **Helpers:** `Tuist/ProjectDescriptionHelpers/` holds `CoreProduct` (the core table),\n  `FocusedApp` (the target template plus `FocusedApp.all`), `LibretroCores` and `DevSettings`. Tuist\n  only finds helpers at `<root>/Tuist/ProjectDescriptionHelpers`; don't add a `Dev/Tuist/`.\n- **How cores get in:**\n  - SwiftPM dynamic cores are embedded with `.package(product:, type: .runtimeEmbedded)`.\n  - Cores from a hand-maintained `Cores/<X>/*.xcodeproj` are linked by `-framework <Product>` in `OTHER_LDFLAGS`.\n    That project is referenced from `Workspace.swift` (Xcode then builds it as an implicit dependency),\n    and the \"Embed core frameworks\" post script copies and signs the framework. Tuist can't\n    depend on targets of projects it doesn't generate.\n- **Libretro dylibs per app:**\n  - `FocusedApp.libretro` names (cores.yml) become a pre script calling\n    `CoresRetro/RetroArch/scripts/get-modules.sh --urls <file>`, plus a post script running\n    `make_frameworks_retroarch.sh` and `validate_frameworks.sh`.\n  - `--urls` keeps its own state dir (`modules_compressed/<iOS|tvOS>-urls-<sha12>`) and never prunes the shared `modules/`.\n  - The buildbot dylibs are iOS-platform binaries and cannot `dlopen` in a simulator, so\n    **Provenance-Dev-Thin plays games on a device only**.\n- **Manifest checks:** `Scripts/dev/check_dev_manifest.sh` compiles the helpers against Tuist's\n  `ProjectDescription.framework` and checks three things: every libretro name is enabled in cores.yml,\n  every path exists, and no local package is declared twice. Prebuilt artefacts (e.g. `PVlibDolphin.xcframework`) are\n  required only for rows a `FocusedApp` uses.\n- **Harness:** `make dev-harness ROM=<path> TARGET=ui|azahar FRAMES=n CORE=<id> SIM_DEVICE=<udid|booted>`\n  builds the app, launches it with the `-PVHarnessROM`, `-PVHarnessCore`, `-PVHarnessFrames`,\n  `-PVHarnessOut` and `-PVHarnessExit` launch arguments, and copies `screenshot.png`, `frames.json`, `log.txt` (or\n  `error.txt`) to `build/harness/<Scheme>/`. `TARGET=thin` is refused (exit 2): Thin runs on a device only.\n  Synthetic test ROMs come from `Scripts/dev/make_harness_rom.py` (2600 and gba). Frame counts are\n  time-based estimates (`\"frameCountSource\": \"estimated\"`). Its first catch was a native Stella VFS\n  regression (`16da1e4c40`).\n- **Core slices:** `Scripts/cores/build_slice.py <azahar|dolphin> <ios|ios-sim|tvos|tvos-sim>\n  [--print-key|--force|--xcframework|--cache-dir DIR]` builds or links one slice.\n  - The key is a content hash: submodule working tree (dirty edits bust the cache; `--force` rebuilds) and\n    nested externals, build script and toolchain, flags, Xcode and SDK, and Azahar's MoltenVK slice.\n  - Dolphin keys only on the dolphin-ios paths its CMake build reads (`DOLPHIN_BUILD_PATHS`: top-level\n    CMake files, `Externals`, `Source/Core`, `Source/iOS/Library`, `ApprovedInis.json`), as HEAD tree\n    hashes plus dirty edits under them. iCube app commits and edits there no longer force a rebuild;\n    if the build starts reading a new path, add it to that list.\n  - The cache is `$PV_CORE_CACHE` or `~/Library/Caches/Provenance/cores/<core>/<slice>/<key12>/`.\n  - On a hit, the legacy `build/xcframework/<Product>-<slice>.framework` path is a symlink into it.\n  - Only `--xcframework` repacks the multi-slice xcframework (Dolphin's is repacked after every miss).\n    If its hard links fail, set `PV_CORE_CACHE` to a folder on the repo's volume.\n- **CI:** `build.yml` and `testflight.yml` cache `~/Library/Caches/Provenance/cores` per platform,\n  keyed on `build_slice.py --print-key`. `dev-workspace.yml` smoke-builds `Provenance-Dev-UI` on\n  both simulators (Tuist via mise, which needs `GITHUB_TOKEN`).\n- **Pruning rule:** no new `Cores/` project without an audit row in\n  `docs/superpowers/specs/2026-10-10-core-audit.md`.\n\n### Azahar core build gotchas\n\n- **Shipping Xcode and Tuist consume `PVAzahar-Dynamic` from `Cores/Azahar/Package.swift`.** Prepare its cached CMake-built `PVlibAzahar.xcframework` before package resolution: `make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim`). `make open`, dev generation, CI and release entry points do this automatically. A cold slice takes ~30–40 min and needs cmake, ninja and python3 ≥ 3.10. The content key includes the submodule/externals, builder, toolchain, Xcode/SDK and MoltenVK slice; a hit reuses native output and refreshes `build/headers`. Force a rebuild with `build_slice.py azahar <slice> --force --xcframework`.\n- **The package graph always declares the native binary.** Missing output must be prepared before resolving; a build phase cannot produce an XCFramework early enough. The package's ObjC++ bridge (`PVAzaharBridge`) must never import the Swift module (`PVAzahar-Swift.h`): that is a target cycle. The Swift core pushes `renderViewClass` and `userDirectoryPath` into it from `init`, and runs its per-load setup (options, `respectsMuteSwitch`, emuThreeDS import) from the bridge's `willLoadFile` hook. A `loadFile` override on a Swift core never runs: `PVEmulatorViewController.createEmulator` calls `bridge.loadFile` for every `ObjCBridgedCore` (and `PVEmulatorCore.loadFile` only throws), which is how 3.4 TestFlight shipped \"Azahar has no user directory\".\n- **PVlibAzahar is arm64-only, and so is every simulator build.** `PVAzahar` excludes x86_64 simulator slices, as do `Build.xcconfig` (the shipping app) and the dev apps; the old link-only x86_64 stub is gone. Package targets ignore project settings, so generic-simulator builds also pass `EXCLUDED_ARCHS=x86_64` to xcodebuild.\n- **Never include azahar's `core/hle/service/nwm/nwm_uds.h`.** Azahar headers come in with `-idirafter`, so its `#include \"network/network.h\"` resolves to the SDK's Network framework header instead. The bridge includes azahar's own copy as `\"network.h\"` via a narrow `HEADER_SEARCH_PATHS` entry.\n- **Emulator changes go on the fork.** Commit them to `Provenance-Emu/azahar` branch `provenance` and list them in `Cores/Azahar/PATCHES.md`. Never edit `Cores/Azahar/azahar` in place.\n- **The tvOS user root is Caches.** `PVAzaharCore.userRootURL` is `Documents/Azahar` on iOS and `Library/Caches/Azahar` on tvOS, where Documents is not writable.\n- **`-fcxx-modules` is on** so the ObjC++ glue can `@import` its PV* dependencies. It must not import `PVAzahar-Swift.h`.\n- **`runOnEmuThreadAndWait:` jobs can outlive their timeout.** Capture only by value or `shared_ptr`, never by reference.\n- **PVAzahar links with `-unexported_symbols_list unexported_symbols.txt` (`__Z*`).** dyld coalesces *exported* weak C++ definitions (template instantiations, inline functions) across every loaded image. PVlibDolphin loads first and exports its own glslang and fmt instantiations from different library versions, and azahar's shader compiler crashed in glslang's parser after calling Dolphin's `addSwizzle`. Keep the C++ surface private; apply the same to any new core framework that static-links glslang, fmt, SPIRV-Cross or similar. PVEmuThree uses the same list. PVlibDolphin cannot (the PVDolphin bridge imports Dolphin's C++ API, including weak `MMU::HostRead<>` instantiations), so dolphin-ios's `BuildiOSXCFramework.py` relinks a second time with a generated list of third-party weak symbols via `-DDOLPHIN_UNEXPORTED_SYMBOLS_LIST`; the global `CMAKE_SHARED_LINKER_FLAGS` never reaches the `dolphin` target. Audit a built app with `nm -m <fw> | grep 'weak external'` per framework and `comm -12` the sorted lists.\n- **Keep C++ visibility filtering at the final framework link.** Compile-time hidden visibility lets Xcode's SwiftPM `ld -r` step localize bridge definitions before the Swift module and engine can resolve them. `Package.swift` forwards `-ObjC` with `-Xlinker` for the Swift link driver; `unexported_symbols.txt` hides C++ symbols in the final image.\n\n### Metal rendering gotchas\n\n- **`CAMetalLayer.nextDrawable` blocks forever when backgrounded.** Metal reclaims drawables during background. The CA runloop observer fires `MTKView.draw(in:)` before `didBecomeActiveNotification`, so `currentDrawable` deadlocks the main thread. ALL `draw(in:)` implementations MUST early-return when `UIApplication.shared.applicationState != .active`.\n- **`@synchronized(self)` in emulation loop vs main thread.** The emu loop holds `@synchronized(self)` around `executeFrame` at 60fps. Any main-thread code that acquires the same lock (e.g., `setPauseEmulation`) will starve. Set atomic flags BEFORE acquiring the lock so the emu loop sees the change and releases.\n- **Thin wrapper reports its geometry/aspect/sample-rate LATE.** `PVThinLibretroFrontend` populates `_rawAVInfo` (geometry, aspect, `timing.sample_rate`, fps) only at the END of `startWithROMPath` — which runs inside `startEmulation`, NOT during `loadFile` (that only dlopens the core). But `createEmulator()` sets up the audio graph (`setupAudioGraph`/`startAudio`) AND the DeltaSkin viewport BEFORE `core.startEmulation()`. So anything that reads the core's geometry/aspect/sample-rate at view/audio-setup time gets the fallback defaults (`?: 640/480/256`, `audioSampleRate → 44100`), not the real values. Native/thick cores populate av_info during `loadFile`, so they don't hit this. This has caused: (1) thin-wrapper audio built at 44100 then never rebuilt → bassy/glitchy (fixed by rebuilding the audio graph after start if the rate changed, #cb40fef061); (2) skin render view stuck full-screen/wrong-size until rotation because the viewport was computed before geometry settled (fixed by recomputing on layout-settle, #a63e1622f1). Pattern for any new feature that reads core geometry/rate: recompute/refresh AFTER `startEmulation`, gated on the value actually having changed.\n\n### Shader / filter system\n\n- **The slang library is fetched at build time, never committed.** `Scripts/shaders/fetch_slang_shaders.py` downloads the release pinned in `Scripts/shaders/slang-shaders.lock.json` (JoeMatt/slang-shaders-metal, `.oecompiledshader` zips compiled from libretro slang by OpenEmu-Shaders' `oeshaders`), unpacks each preset into a `shader.json` directory (no runtime unzip), merges the hand-written presets in `PVShaders/Library/native-metal/`, and writes `manifest.json`. Every app target (shipping xcodeproj and Tuist `FocusedApp`, `slangShaders:`) copies it into the bundle as `SlangShaders/` via `embed_slang_shaders.sh`. To bump the snapshot: change the lock, run `Scripts/shaders/validate_slang_shaders.sh` (renders every preset on the Mac GPU, writes `slang-shaders-excluded.json`), commit both.\n- **Which filter draws is per game.** `ShaderLibraryStore` (PVShaders) holds selections for global / `system:<id>` / `game:<md5>` scopes (game wins), parameter overrides, favorites and recents; selections, parameters and favorites sync through iCloud KVS as timestamped per-key maps (`StampedMap`, merged both ways via `iCloudSettingsSync.register`). Native filters keep their values in their old `Defaults` keys. The legacy `metalFilterMode` key is still bridged into the global selection; its raw values are case names (`Always(vhs)`, not `Always(VHS)`).\n- **`ShaderSelection.off`, not `.none`.** A `.none` case on an enum passed as `ShaderSelection?` silently becomes `Optional.none` and clears the scope.\n- **Renderers:** `PVMetalViewController` resolves the filter every frame (native → `PVMetalFilterRenderer`, library → `SlangFilterRenderer`, which compiles presets off the main thread and draws the plain picture until ready). Cores that present from their own thread (Dolphin Metal backend, native PPSSPP) use `ShaderPostProcessor`, fed a resolved snapshot on `ShaderLibraryStore.didChangeNotification`. Dual-screen layouts and Azahar are never filtered (pause tiles hidden via `filtersApplyToCurrentLayout`); `LCDScreenFilter` (CIFilter) is the intended DS/3DS look.\n- **Filters draw into the picture rect, not the drawable.** Until a skin reports its screen rect the GPU view is full screen; `RenderGeometry.contentRect` letterboxes the picture inside it. Code that draws over the frame must use the same rect (this was the \"VHS covers the whole screen\" bug).\n- **`swift test` in PVShaders runs real Metal tests on the host GPU**, but the SwiftPM CLI only copies `.metal` files: the slang runtime compiles its bundled source as a fallback, while native-filter pipelines (package metallib) exist only in Xcode builds, so those tests skip.\n\n### Lookup databases (PVLookup)\n\n- **The databases are fetched at build time, never committed.** `Scripts/lookup/databases.lock.json` pins five assets by sha256 on a `databases-YYYY-MM-DD` release of Provenance-Emu/PVLookup. Every app target (shipping xcodeproj \"Script: Embed Lookup Databases\", Tuist `DevSettings.embedLookupDatabasesScript`) runs `embed_lookup_databases.sh`, which fetches them into the gitignored `LookupDatabases/` (cache: `~/Library/Caches/PVLookupDatabases`) and copies the zips to the bundle ROOT, where PVLookup's default `LookupDatabases` source looks for `<file name>.zip` in `Bundle.main`. No `LookupDatabases.configure` call is needed. `make lookup-databases` fetches by hand.\n- **`fetch-lookup-databases.sh` is a copy of PVLookup's canonical script**; keep it identical. To bump the databases: publish a new release in the PVLookup repo, then update the lock file.\n- **The cheat database generator still lives here.** `make update-cheatdb` writes `build/cheatdb/libretro_cheats.sqlite.zip` for publishing as a PVLookup release asset; the app never builds from it.\n- **PVLookup logs through `PVLookupLog`**, routed into PVLogging by `LoggingBootstrapTask` on a serial queue: PVLogging's `log()` holds four `Logger` interpolations, and calling it at the bottom of a deep lookup overflowed a 512 KB concurrency stack in a Debug build (iFly).\n- **`PVLookupTypes` and `TheGamesDB` are not products.** Their imports in PVLibrary/PVUI resolve through the `PVLookup` product's transitive modules, as they always did.\n\n### Debugging emulator cores\n\n- **\"Black screen\" reports: ask for the `[NO-FRAME]` log line.** Five seconds after a successful boot with nothing presented, `PVEmulatorViewController+FirstFrameWatchdog` logs the presenter, render path, pause state, geometry and the GPU view's frame/visibility. Cores that draw to their own view (native Dolphin, PPSSPP, RetroArch `skipLayout`) trip it by design.\n- **Per-game core options depend on `CoreOptionsContext.currentGameMD5`.** It is set in `initCore()` before the core initializes and cleared in `quit`; `CoreOptional.currentGameMD5` defaults to it. Without it a core reads only the core-wide key and per-game overrides are silently ignored.\n- **flycast cannot be debugged with Xcode attached.** It installs `signal(SIGSEGV, ...)` for VRAM lazy-mapping; Xcode catches SIGSEGV and pauses, breaking the core. Use Console.app (filter `Process = Provenance`) for live logs OR `iOS Settings → Privacy & Security → Analytics → Analytics Data` for `.ips` crash files post-mortem.\n- **Sentry's `enableCrashHandler` is disabled** at `SentryBootstrapTask.swift:48` because its SIGSEGV handler conflicts with flycast's MMU path. Do NOT re-enable it. Crash telemetry flows through MetricKit instead.\n- **iPad MoltenVK surface_caps lie.** On iPadOS 26 with Stage Manager / adaptive scaling, `VkSurfaceCapabilitiesKHR.currentExtent` / `minImageExtent` / `maxImageExtent` ALL report `view.bounds × contentScaleFactor`, NOT the actual `CAMetalLayer.drawableSize`. They can differ (e.g. 2732×2048 vs 2092×1568). The authoritative source for iOS Vulkan is `metalLayer.drawableSize`. Never clamp against MoltenVK surface caps on iOS.\n- **libretro core option key prefixes don't always match the core name.** flycast's options are `reicast_*` (per `CORE_OPTION_NAME` in `shell/libretro/libretro_core_option_defines.h`). Always grep the core's `libretro_core_options.h` before guessing key names.\n- **Jaguar numpad uses keyboard input.** The upstream `virtualjaguar-libretro` core reads numpad buttons 0-9/*/# via `RETRO_DEVICE_KEYBOARD` (`RETROK_0`-`RETROK_9`, `RETROK_MINUS`, `RETROK_EQUALS`), NOT joypad buttons. Buttons 7-9/*/# have NO joypad mapping. The thin wrapper dispatches both keyboard events (via `_bridge.setKeyState`) and joypad presses for compatibility.\n\n### Code conventions\n\n- **Library storage: `LibraryLocation` owns the roots; never move library folders by hand.** `URL.documentsPath`/`cachesPath` and `LibraryLocation.current` share one launch-fixed precedence (stored `libraryLocation` → legacy `useAppGroups`, both snapshotted at launch). Flipping `useAppGroups` alone no longer moves files.\n  - Moves go through `LibraryMoveCoordinator` (PVFileSystem `LibraryMove/`): journaled in the app's Application Support, verified (size; MD5 under `ROMs/`+`BIOS/`), switched by writing the setting, and cleaned up on the *next* launch after a delta sweep.\n  - Leaving the App Group moves every top-level entry except hidden files, `.realm` names and the journal, including entries created after the move started. \"Identical\" means size **and** MD5, in every folder. A copy-phase conflict moves the destination's own file aside to `Conflicts/` and puts the live file at the real path; the post-switch sweep files a changed source under `Conflicts/`. Nothing is ever deleted or overwritten, and copies go through a hidden temp file and a rename.\n  - \"RESET TO DEFAULT\" must not touch `libraryLocation`/`useAppGroups`. Lite and the full app share the group, so a move keeps a copy while the other app still uses it.\n  - A folder picked in Files is `LibraryLocation.pickedFolder`: the security-scoped bookmark is `Defaults[.libraryPickedFolder]`, resolved once per launch by `LibraryLocation.pickedFolderAtLaunch` (never stopped; a failure is `.unavailable` and the UI offers re-pick / App storage / retry). It means two roots: `rootURL` for `ROMs/`, `BIOS/`, `System/` and `savesRootURL` for the five saves folders, which join the picked folder only when `includesSaves` is on. `LibraryFolderInspector` gates every pick (writable, not the library itself, not inside a moved folder). The journal is version 2 (`sourceFolder`, `destinationFolder`, `deduplicateByMD5`, `remaps`).\n  - Joining a folder that already has `.provenance-library.json` de-duplicates `ROMs/` and `BIOS/` by size + MD5 under any name; the resulting `remaps` must be applied to the database before cleanup deletes sources, so a remap applier (`LibraryPathRemapper`) has to be registered before `resumeIfNeeded()` (`PVGameLibrary.init` does this). iCloud-located picked folders are watched by `PickedFolderCloudWatcher`.\n- **Changing a game's MD5 goes through `GameRekey`, never a delete.** `PVGame.md5Hash` is the Realm primary key, so a ROM rewritten in place (\"Replace the Original\") needs a new row: `GameRekey.rekey(gameWithMD5:to:crc:fileSize:in:)` copies the game (keeping `id`), re-points saves, cheats, recents, patches, libraries and controller profiles, then raw-deletes the old row. `RomDatabase.delete(game:)` would delete the ROM file, saves and patches. The library MD5 is the importer's (`GameImporterDatabaseService.calculateMD5(forGame:)`, header-aware), not the raw file MD5. Replacing is blocked while iCloud Sync is on: another device's soft-delete of the old game removes the file at the shared path.\n- **Notification names — no magic strings.** Declare `FOUNDATION_EXPORT NSNotificationName const FooBarNotification;` in an ObjC `.h`, define in the matching `.mm`, mirror via `Notification.Name` extension in Swift referencing the same string. Posters reference the const; observers reference `Notification.Name.fooBar`. Single string literal lives in one `.mm` — never at a call site.\n- **Email in public-facing files / comments:** use `git@joemattiello.com` (SECURITY.md, README, GitHub issue comments, anything that ships publicly).\n- **ObjC categories in SwiftPM modules can be silently elided.** When a category is declared in a separate `.h` file referenced by a modulemap, Swift module synthesis may drop it entirely — no error, just \"no member\" at call sites. Workaround: declare methods in the main `@interface` block. Known to affect `(LightGun)` categories on PVStellaBridge and PVProSystemGameCore. Other categories (Cheats, Trackball) in the same files work fine — root cause unclear.\n\n### Subagent worktree rules\n\n- Subagents in `isolation: worktree` MUST stay on their own branch — never `git reset` / `rebase` / `push` / touch `develop`. Their commits get cherry-picked onto develop afterward.\n- Always include \"DO NOT git reset / rebase / push / touch develop\" in subagent prompts. Without it, parallel agents have historically collided (one agent ran `git reset HEAD~1` and dropped another agent's commit).\n- After an agent reports completion: cherry-pick from the agent's branch onto develop in the MAIN worktree's `Provenance/` directory. Shell can land in the agent's worktree if you're not careful — verify `pwd` and `git branch --show-current` before each cherry-pick.\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# PVUI unit tests (PVUIBaseTests + PVSwiftUITests). Use the committed\n# PVUI-UnitTests scheme, not the auto-generated PVUI one: that also builds the\n# snapshot suite, whose Prefire-generated code doesn't compile. On Xcode 26.6\n# realm-core needs `-xcconfig` with OTHER_CFLAGS/OTHER_CPLUSPLUSFLAGS =\n# $(inherited) -Wno-invalid-specialization (CI's Xcode 26.3 doesn't).\ncd PVUI && xcodebuild test -scheme PVUI-UnitTests \\\n  -destination 'platform=iOS Simulator,name=iPhone 17' \\\n  CODE_SIGNING_ALLOWED=NO -skipPackagePluginValidation -skipMacroValidation\n\n# PVLibrary tests: the committed PVLibrary-UnitTests scheme, through the\n# package workspace (PVLibrary/PVLibrary.xcodeproj shadows Package.swift, and\n# the auto-generated scheme has no test action). Same realm-core -xcconfig on\n# Xcode 26.6. CI: pvlibrary-tests.yml (develop pushes + PRs via agent-validation).\ncd PVLibrary && xcodebuild test -workspace .swiftpm/xcode/package.xcworkspace \\\n  -scheme PVLibrary-UnitTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  -collect-test-diagnostics never CODE_SIGNING_ALLOWED=NO \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# PVCoreBridgeRetro (thin wrapper) tests: PVLibRetroTests in Package.swift.\n# Open the package in Xcode and select its test scheme, or use its package workspace.\n# Ad-hoc signing is required for MoltenVK.\nxcodebuild test -workspace PVCoreBridgeRetro/.swiftpm/xcode/package.xcworkspace -scheme PVCoreBridgeRetro \\\n  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  CODE_SIGN_IDENTITY=- CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM= \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Agents should scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode (PVShaders: `swift test` works) |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **GitHub workflow files** — `.github/workflows/*.yml` may be edited when fixing CI issues or optimizing builds. However, GitHub Actions bot PRs cannot push workflow changes (requires `workflows` permission) — if an agent PR needs to include workflow changes, note this in the PR description so a maintainer can push them manually.\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are third-party upstream code (do not casually fork in place)\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **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. Use `PBXFileSystemSynchronizedRootGroup` for source directories (Xcode 16+). Prefer minimal diffs — only touch the sections that need changing.\n\n### Minimum Deployment Targets\n\nProvenance ships for **iOS 17+ and tvOS 17+**. The PV* packages also declare **macOS 14+ and visionOS 1+** so keep code compiling for them with platform guards, but no Catalyst/native-mac/visionOS app target ships today (native macOS is planned — see the 2026-08-07 macOS strategy spec). 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.\n\n**Prefer modern Swift/SwiftUI APIs** when the minimum deployment target supports them:\n\n| Prefer (iOS 16+/17+) | Over (older) |\n|---|---|\n| `ShareLink` | `UIActivityViewController` wrapped in `UIViewControllerRepresentable` |\n| `@Observable` macro (iOS 17+) | `@ObservableObject` + `@Published` |\n| `NavigationStack` | `NavigationView` |\n| `.navigationBarTitleDisplayMode(.inline)` on non-tvOS | conditional guard |\n| `UIWindowScene.keyWindow` | `UIApplication.shared.keyWindow` (deprecated iOS 13) |\n\n**`@StateObject` vs `@ObservedObject` rule:**\n- `@StateObject` — use only when the view **creates and owns** the object's lifetime (new instances).\n- `@ObservedObject` — use for **singletons** (e.g. `Foo.shared`) and objects passed in from outside.\n  Using `@StateObject` with a singleton is semantically wrong even though it compiles.\n\n### Pre-PR Validation (MANDATORY)\nAgents MUST run these checks before creating a PR. Do NOT skip any step.\n\n1. **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).\n\n2. **Lint** — Run `swiftlint lint --path <file>` on every changed Swift file.\n\n3. **Unit tests** — If the module has tests, run them: `cd PV<Module> && swift test`. If adding new logic, add test coverage.\n\n4. **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.\n\n5. **No magic numbers** — Extract constants. Don't hardcode values that are used in multiple places.\n\n6. **Use `SystemIdenti",".github/copilot-instructions.md":"# Copilot Instructions for Provenance\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.2 (`/Applications/Xcode_16.2.app`)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores\n- Each has iOS and tvOS variants\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVUI** — SwiftUI-based shared UI components\n- **PVSettings** — User preferences\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVSupport** — Shared utilities\n- **PVLogging** — Logging infrastructure (OSLog-based; use `DLOG`/`ILOG`/`ELOG`/`WLOG`)\n- **PVPrimitives** — Base data types shared across modules\n- **PVCheevos** — RetroAchievements integration\n- **PVHashing** — ROM file hashing for identification\n- **PVLookup** — Game metadata lookup (separate repo, github.com/Provenance-Emu/PVLookup; databases fetched at build time via `Scripts/lookup/databases.lock.json`)\n- **PVJIT** — JIT compilation support for emulator cores\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Code Style\n\n### Swift Code\n- Prefer `async/await` over Combine where possible\n- Use `@Sendable` and proper actor isolation for concurrency\n- Avoid force unwraps (`!`) in production code; use `guard let` or `if let`\n- Line length limit: 200 characters (`.swiftlint.yml`)\n- 4-space indentation, no indent for `case` in `switch`\n\n### Emulator Core Bridges (`*Bridge+Controls.mm`)\n- All controller types must be handled: Extended, Micro, Keyboard\n- Button mappings must match upstream emulator constants\n- Never modify upstream submodule source in `Cores/<name>/<upstream-dir>/`\n\n## Common Patterns\n\n### Logging\nUse PVLogging functions — never `print` or `NSLog`:\n```swift\nimport PVLogging\nDLOG(\"Debug: \\(value)\")    // Debug builds only\nILOG(\"Info: \\(message)\")   // Info\nWLOG(\"Warning: \\(msg)\")    // Warning\nELOG(\"Error: \\(error)\")    // Error\nVLOG(\"Verbose detail\")     // Debug builds only (verbose)\n```\n\n### Realm Threading\nRealm objects are thread-confined — never pass them across threads:\n```swift\n// Wrong — crashes across threads:\nlet game = realm.objects(PVGame.self).first!\nTask.detached { _ = game.title } // CRASH\n\n// Right — pass primary key and re-fetch on new thread:\nlet gameID = game.id\nTask.detached {\n    let realm = try await Realm()\n    let game = realm.object(ofType: PVGame.self, forPrimaryKey: gameID)\n}\n\n// Or freeze for read-only cross-thread access:\nlet frozenGame = game.freeze()\n```\n\n### Feature Flags\nGate new features behind `PVFeatureFlags` (Tier 0, importable anywhere):\n```swift\nimport PVFeatureFlags\n\n// Check at runtime (PVFeatureFlags is @MainActor):\nif await PVFeatureFlags.shared.isEnabled(.myFeature) { ... }\n\n// In SwiftUI views via environment:\n@Environment(\\.featureFlags) var featureFlags\nif featureFlags.myFeature { ... }\n```\n\n### Actor Isolation\nUI updates must happen on the main actor:\n```swift\n@MainActor\nclass MyViewModel: ObservableObject { ... }\n\n// From an async context:\nTask { @MainActor in\n    self.isLoading = false\n}\n```\n\n### Platform-Specific Code\n```swift\n#if os(tvOS)\n    // tvOS: no touch input; use Focus Engine and game controllers as primary input\n#elseif os(iOS)\n    // iOS: touch gestures + game controllers\n#endif\n```\n\n## Platform Support Matrix\n\n### Primary Targets (always must compile and work)\n- **iOS 17+** — minimum deployment target\n- **tvOS 17+** — minimum deployment target\n\n### Aspirational Targets (compile where possible, especially non-UI code)\n- **macOS 14+** (Sonoma) — Designed for iPad today; native target planned; Catalyst not supported\n- **watchOS 10+** — limited scope; non-UI utility code only\n- **visionOS 1+** — Aspirational — package declarations + #if os(visionOS) guards only; no app target\n\n### Unit Test Platform\n- **Linux** — Tier 0–2 modules should compile on Linux for CI unit tests via `swift test`\n- Avoid Darwin-only APIs in Tier 0–2 without `#if canImport(Darwin)` guards\n\n### Guards\n```swift\n#if os(iOS)\n    // iOS-only (touch, UIKit specifics)\n#elseif os(tvOS)\n    // tvOS-only (Focus Engine, no touch)\n#elseif os(macOS)\n    // Mac-only\n#elseif os(watchOS)\n    // watchOS-only\n#elseif os(visionOS)\n    // visionOS-only\n#endif\n```\n\n## GitHub Project & Issue Workflow\n\n### Project Board\nAll issues and PRs should be tracked on the [Provenance Roadmap](https://github.com/orgs/Provenance-Emu/projects/1/views/1).\n\n### Labels\nUse these labels consistently:\n- `bug` — regressions or broken behavior\n- `enhancement` — new features or improvements\n- `Epic` — parent issue grouping related sub-tasks\n- `agent-work` — issue assigned to Claude/AI agent lane\n- `cursor-work` — issue assigned to Cursor agent lane\n- `ai-reviewing` — PR currently being reviewed by AI\n- `ai-reviewed` — PR review complete\n- `needs-fixes` — review found issues requiring changes\n- `ready-for-review` — AI review passed, awaiting @JoeMatt\n\n### Epics & Sub-tickets\n- Epic issues use the `Epic` label and list sub-tasks as checkboxes in the body\n- Sub-tasks reference their parent epic with \"Part of #EPIC_NUMBER\" in the PR/issue body\n- Close sub-tasks by merging their PR; epic auto-closes when all checkboxes are checked\n\n### Tagging Reviewers\n- Tag **@JoeMatt** for final human review on any significant change\n- Tag **@copilot** in a PR comment to request a Copilot review pass\n- Tag **@claude** in a PR comment to request a Claude fix/review pass\n- Do NOT tag both simultaneously — one at a time\n\n### WhatsNew / Release Notes\nSee \"## WhatsNew & Release Versioning\" section below for rules.\n\n## WhatsNew & Release Versioning\n\n### CRITICAL: Never Guess Version Numbers\n\nThe `whats-new.json` file (`PVUI/Sources/PVSwiftUI/Resources/whats-new.json`) drives\nthe in-app \"What's New\" sheet. Agents MUST NOT add entries with speculative future version\nnumbers. **The current released version is the latest git tag** — check with:\n```bash\ngit tag --sort=-v:refname | head -1\n```\n\n### Rules for Agents\n1. **Only add a `whats-new.json` entry when**: the PR contains a major user-visible feature AND the target version has been explicitly confirmed in the issue/PR\n2. **Never invent version numbers** — if unsure, skip the entry and note it in the PR body with `TODO: add whats-new entry for version X.Y.Z`\n3. **Version format**: semver string matching the Xcode `MARKETING_VERSION` (e.g. `\"3.3.0\"`)\n4. **Max 6 features** per version entry\n5. **Do NOT modify existing entries** for already-shipped versions\n\n### Changelog Fragments\nPrefer adding `.changelog/<PR_NUMBER>.md` fragments over editing `CHANGELOG.md` directly.\nThe consolidation workflow (`consolidate-changelog.yml`) merges fragments on PR merge.\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are upstream code\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **project.pbxproj** — avoid when possible; prefer SPM Package.swift changes\n\n### What NOT to Flag in Code Review\n- `force_cast` and `force_try` are intentionally warnings, not errors\n- Generated files (`Version.h`, `Version.swift`) — skip review\n- Submodule contents (`Cores/<name>/<upstream>/`) — skip review\n- `project.pbxproj` changes — minimize review noise\n\n### PR Requirements\n- Target the `develop` branch\n- Include test coverage for new logic (where testable)\n- Keep scope focused — one logical change per PR\n- Run `swiftlint` on changed files before submitting\n- Agent PRs should use the `[Agent]` prefix in title\n\n### Branch Naming & Commit Messages\n- Branches: `agent/issue-<N>` for agent work, `feature/<description>` for features\n- Commits: Use conventional commits (`fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`)\n- Keep commit messages concise (< 72 chars for subject line)\n","AGENTS.md":"# AGENTS.md\n\n## Start here\n- Read `.cursorrules` and `CLAUDE.md` before making changes.\n- Keep scope to one logical change per task.\n- Target the `develop` branch and use conventional commits: `fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`.\n- Prefer the lowest dependency tier that can satisfy the task.\n\n## Project snapshot\n- Provenance is an iOS/tvOS emulator frontend built from many `PV*` Swift packages plus Objective-C / Objective-C++ bridge layers for emulator cores.\n- Main app targets live in `Provenance/`, `ProvenanceTV/`, and `Extensions/`.\n- Emulator bridge work usually lives under each core directory in `Cores/<CoreName>/` or in `CoresRetro/RetroArch/`.\n- Persistence uses Realm and CloudKit. Realm objects are thread-confined; pass object identifiers or frozen objects across threads.\n\n## Hard constraints\n- Do not modify upstream submodule source under `Cores/<name>/<upstream-dir>/`.\n- Do not modify `CodeSigning.xcconfig`.\n- Avoid `project.pbxproj` changes unless there is no viable package-based alternative.\n- Prefer `Package.swift` updates over Xcode project edits whenever possible.\n- Avoid touching generated files such as `Version.h`, `Version.swift`, or build output under generated directories.\n- Preserve existing comments and logic unless the task explicitly requires changing them.\n\n## Module tiers\n- Tier 0: `PVObjCUtils`, `PVFeatureFlags`, `PVCheevos`\n- Tier 1: `PVLogging`, `PVPlists`, `PVHashing`\n- Tier 2: `PVSettings`, `PVPrimitives`\n- Tier 3: `PVSupport`, `PVAudio`, `PVCoreAudio`\n- Tier 4: `PVCoreBridge`, `PVEmulatorCore`, `PVShaders`\n- Tier 5: `PVCoreBridgeRetro`, `PVCoreLoader`, `PVLibrary`\n- Tier 6: `PVUI`, app targets\n\n## Swift and platform conventions\n- Use 4-space indentation and keep lines within the repo's 200-character limit.\n- Prefer `async` / `await` for new asynchronous work.\n- Prefer `guard` / optional binding over force unwraps in production code.\n- When adding or changing Swift APIs, add concise `///` documentation comments where they help future readers.\n- Prefer protocol-oriented designs when they simplify the implementation.\n- Assume Swift changes must work for both iOS and tvOS. Guard unsupported APIs with conditional compilation.\n- Do not add comments that merely restate the code.\n\n## Validation guidance\n- For Tier 0-2 modules, validate locally with `swift build` or `swift test` inside the module when the change is testable.\n- Run `swiftlint lint --path <changed-swift-file>` for changed Swift files when `swiftlint` is available.\n- For higher-tier modules and app targets, validate from `Provenance.xcworkspace`.\n- Start with the `Provenance-Lite` scheme for the fastest Xcode validation. Lite is an internal test build (never published, no users): never release-gate on it or build Lite-only migrations.\n- If an initial Xcode build fails because generated sources are missing, retry once before investigating further.\n- Do not run full iOS/tvOS command-line builds unless the user explicitly asks for them.\n\n## Cursor Cloud specific instructions\n- Cursor Cloud agents here run on Linux, so Xcode, Simulator, and Apple platform runtime validation are unavailable.\n- In this environment, fully validate docs changes and any standalone Swift Package work that can run without Xcode.\n- For workspace-level iOS/tvOS changes that require Xcode, do the best possible static review, run any repo checks that are available, and clearly call out the Mac-only validation that still needs to happen.\n- Never claim an app runtime or simulator change is verified unless it was actually exercised on macOS with Xcode.\n\n## Quick commands\n- `swiftlint lint --path <file>`\n- `cd PV<Module> && swift build`\n- `cd PV<Module> && swift test`\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/plugin.json)"},"files":{".cursorrules":"You are working on Provenance, a multi-platform emulator frontend for iOS/tvOS.\n\n## Key Rules\n- Read CLAUDE.md for full project conventions, module tiers, and architecture\n- Target the `develop` branch for all PRs\n- Use conventional commits: fix:, feat:, chore:, build:, refactor:, test:, docs:\n- Do NOT modify third-party submodule source in `Cores/<name>/<upstream-dir>/`\n- Do NOT modify project.pbxproj unless absolutely necessary\n- Do NOT modify CodeSigning.xcconfig\n- Prefer SPM Package.swift changes over Xcode project changes\n- Keep changes focused — one logical change per PR\n\n## Swift Conventions\n- 4-space indent, no indent for case in switch\n- Line length: 200 chars max\n- Prefer async/await over Combine for new code\n- Use guard let / if let, avoid force unwraps in production code\n- Realm objects are thread-confined — pass Object IDs across threads\n\n## Build\n- Open Provenance.xcworkspace (not .xcodeproj)\n- Start with Provenance-Lite scheme for fastest builds\n- Initial builds may fail due to lazy code generation — retry once\n- Never build iOS/tvOS from command line unless explicitly asked\n\n## Module Tiers (lowest = fewest dependencies)\n- Tier 0: PVObjCUtils, PVFeatureFlags, PVCheevos\n- Tier 1: PVLogging, PVPlists, PVHashing\n- Tier 2: PVSettings, PVPrimitives\n- Tier 3+: PVSupport, PVAudio, PVCoreBridge, etc.\n- Scope changes to the lowest tier possible\n\n## Testing\n- swift test in standalone SPM modules (Tier 0-2)\n- Xcode workspace build for higher-tier modules\n- Run swiftlint on changed files before submitting\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.x or Xcode 26+ (use `xcode-select -p` to confirm active version; both are supported)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n- Minimum targets: iOS 17+, tvOS 17+ mandatory. macOS today = \"Designed for iPad\" (the iOS binary on Apple Silicon); a native macOS target is planned (see docs/superpowers/specs/2026-08-07-macos-visionos-strategy-design.md). Mac Catalyst is NOT supported. visionOS/watchOS/Linux: SPM packages declare them — keep code compiling with guards, but no app ships for them.\n\n### Code Signing\nCopy `CodeSigning.xcconfig.sample` to `CodeSigning.xcconfig` and fill in your developer account details.\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\n**Azahar native artifact preparation:** before opening or resolving the shipping workspace, run\n`make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim` for the intended destination).\n`make open` prepares iOS by default. The command reuses the native slice cache and packs its XCFramework;\nSwiftPM owns the wrapper, bridge, Core.plist and migrator test targets. Tuist's `make dev-azahar` performs\nthe same preparation through `dev-generate`. Native engine changes require preparing again before building.\nArm64 simulator builds require `ARCHS=arm64` or `EXCLUDED_ARCHS=x86_64` at the build invocation level.\n\n**Dolphin native artifact preparation:** run `make prepare-dolphin DOLPHIN_SLICE=ios`\n(or `tvos` for a tvOS device) before resolving the shipping workspace. `make open`\nprepares both Azahar and Dolphin. Shipping and Tuist share `PVDolphin-Dynamic`;\nSwiftPM owns the bridge, wrapper, Core.plist, Config and Sys resource bundles.\n`make dev-dolphin` prepares its selected slice before generating Tuist.\n\n**PPSSPP:** shipping and Tuist share `PVPPSSPP-Dynamic`. SwiftPM compiles the native\nengine, its dependencies and the Swift/ObjC++ wrapper, and owns Core.plist and PSP\nassets. No CMake/Xcode project preparation is needed. `make dev-ppsspp` opts into\nthe native focused iOS device app; Thin retains the independent libretro core.\nThe checked-in FFmpeg XCFramework includes arm64 device and simulator slices for\niOS and tvOS. Rebuild it with `Scripts/cores/build_ppsspp_ffmpeg.sh`; the legacy\ncodec headers stay private to PPSSPP.\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores, not really used but should be kept updated regardless\n- Each is a multi-platform 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\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n- **CI cache can shadow source changes.** `actions/cache@v4` caches `Cores/`, `CoresRetro/`, `Dependencies/`, `PVRcheevos/` for submodule speed. A post-restore step runs `git checkout HEAD -- Cores CoresRetro Dependencies PVRcheevos` to un-shadow parent-repo-tracked files. If a CI build ignores your source changes, check that this step ran successfully.\n\n- **xcbeautify hides Run Script output — don't debug CI from the console log.** The build pipes through `2>&1 | tee /tmp/xcodebuild.log | xcbeautify`, and xcbeautify filters out Run Script phase output. `GetModule:` / `MakeFrameworks:` lines therefore never appear in `gh run view` or `gh api .../jobs/<id>/logs`, even when the script ran fine — an empty grep means \"filtered\", not \"never ran\". Those lines exist only in the raw log, which is uploaded on failure as artifact `xcodebuild-log-<IPA_NAME>` (3-day retention):\n\n  ```bash\n  gh run download <run-id> -n xcodebuild-log-Provenance-iOS\n  ```\n\n  The failure-report step also prints, in order: `: error:` diagnostics, the `The following build commands failed:` block, and the last 100 raw lines. It greps `\": (fatal )?error:\"` with a **leading colon** on purpose — a bare `\"error:\"` also matches ObjC selectors like `loadStateToFileAtPath:error:` inside `-Wincomplete-implementation` warnings, which floods the report and buries the real errors. Run Script failures (e.g. \"Generate Frameworks\" exiting non-zero) produce no `error:` line at all and surface *only* in the `build commands failed` block.\n\n- **Symbolicating a crash log.** `release.sh` (local and `testflight.yml`) copies each archive's dSYMs to `build/export/<version>-<build>/dSYMs/<ios|tvos>/` and uploads them to Sentry (`provenance-emu/provenance`, needs `SENTRY_AUTH_TOKEN` or `~/.sentryclirc`; a failed upload only warns). Sentry's crash handler is off, so crashes arrive via MetricKit and on-device `.ips` files. To symbolicate by hand, match the `.ips` binary UUID to a dSYM (`dwarfdump --uuid <x>.dSYM`), then `xcrun atos -arch arm64 -o <x>.dSYM/Contents/Resources/DWARF/<name> -l <load address> <address>` (frames in an embedded framework need that framework's dSYM). Prebuilt binaries — libretro buildbot `*.libretro.framework`, ffmpeg, MoltenVK, Sentry, `PVlibDolphin-ios` — ship no dSYM and stay unsymbolicated.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVAppIntents** — Siri and App intents\n- **PVAudio** — Swift audio apis\n- **PVCheevos** — RetroAchievements API integration\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVFeatureFlags** — Feature flags manager\n- **PVHashing** — ROM file hashing for identification\n- **PVHelp** (separate repo, github.com/Provenance-Emu/PVHelp, shared with iCube) — wiki navigation, page fetch/cache and GitBook preprocessing. App-neutral: Provenance's URLs, page paths and offline SUMMARY.md are `ProvenanceWiki` in PVUIBase (`Resources/WikiContent`)\n- **PVJIT** — JIT compilation support for emulator cores\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVLogging** — Logging infrastructure (CocoaLumberjack-based)\n- **PVLookup** (separate repo, github.com/Provenance-Emu/PVLookup, shared with iFly and iCube) — game metadata, artwork and cheat lookup over libretrodb, OpenVGDB, TheGamesDB, ShiraGame and the libretro cheat database. Ships no database bytes (see \"Lookup databases\" below). Change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVLibrarySnapshot** (separate repo, github.com/Provenance-Emu/PVLibrarySnapshot, shared with iCube and iFly) — the App Group snapshot and cover JPEGs the tvOS Top Shelf reads. Provenance's configuration (app group, `provenance://open?md5=` launch URL) is `PVLibrarySnapshotConfig` in PVAppIntents; PVUIBase `LibrarySnapshotWriter` (tvOS only for now) writes it from `WidgetDataWriter.writeFromRealm()`. Widgets and Quick Look still read `PVLibrarySnapshotLegacy` (`widget.*` keys), whose type names collide with the shared package's: no file may import both\n- **PVNetplay** — Central netplay support code for retroarch and game center\n- **PVPLlists** — Core and System plist processing and other serializer\n- **PVPatching** — ROM/disc patches (IPS, IPS32, BPS, UPS, PPF, xdelta3): import, matching, cache, Patch Applier, patch creation, online catalog\n- **PVPatchEngine** — the streaming patch engine PVPatching applies through (bounded memory, 64-bit offsets, vendored Apache-2.0 xdelta3). Its own repo, github.com/Provenance-Emu/PVPatchEngine, which iCube and iFly also depend on by URL: change it there, tag a release, bump the version in `PVPatching/Package.swift`. Only `PVPatching/Sources/PVPatching/Engine/EngineBridge.swift` may import it (its `PatchFormat`/`PatchError`/`PatchProgress` clash with PVPatching's)\n- **PVPrimitives** — Base data types shared across modules\n- **PVSystems** (separate repo, github.com/Provenance-Emu/PVSystems) — `SystemIdentifier` and per-system metadata (TheGamesDB/libretrodb/OpenVGDB IDs, RetroAchievements console IDs, per-game BIOS manifest). Shared with iCube and iFly: change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVQuicklookSupport** — iOS and macOS Quicklook api support code\n- **PVRcheevos** — RetroAchievements C client integration\n- **PVSettings** — User preferences\n- **PVShaders** — video filters: Provenance's hand-written Metal filters, the bundled libretro slang library (runtime in `PVSlangRuntime`), filter catalog/selection store, previews, non-linear stretch\n- **PVSupport** — Shared utilities\n- **PVThemes** — UI Theming Support\n- **PVTouchOverlay** — Programmatic on-screen controller: layout families, live editor, shared touch primitives\n- **PVUI** — SwiftUI-based shared UI components\n- **PVWebServer** — Swift/Objective-C SwiftPM module for GCDWebServer and WIP new Swift webserver for webdav and http file management, future REST API\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n**Core taxonomy — not every `Cores/PV*` directory is an active shipping core.**\n- **Active native PV* cores** (custom forks or long-supported legacy we\n  actively extend): Azahar, Dolphin, FCEU, Genesis-Plus-GX, Mednafen,\n  Mupen64Plus, ProSystem, snes9x, Stella, plus mGBA, PicoDrive and TGBDual\n  for native-only features. Source of truth:\n  `docs/superpowers/specs/2026-10-10-core-audit.md` (KEEP rows). Retired\n  cores live on as `PVCore.retiredCores` entries (`RetiredCoreMigration`,\n  `RetiredBatterySaveMigration`); `Cores/Debug` stays for its `PVDebug.c` simulator stub; no new `Cores/` project without an audit row.\n- **Deprecated native cores** (`PVCore.deprecatedCores`): Atari800, Bliss,\n  CrabEMU, Gambatte and VisualBoyAdvance-M still ship because they shipped in\n  3.3.x and users have save states on them that the libretro dylibs cannot\n  load, but the libretro cores are the recommended choice. Pickers label them \"(Deprecated)\" and sort them last;\n  nothing migrates away from them. Don't extend them.\n- **Pruned (2026-10, `feature/prune-dead-cores`):** 27 `Cores/` projects that\n  duplicated a libretro buildbot dylib or were never wired were deleted:\n  PokeMini, VirtualJaguar, O2EM, Desmume2015, melonDS, BeetlePSX, DosBox, DuckStation,\n  FreeIntv, GameMusicEmu, Gearcoleco, JollyGoodEmulation, Mini_vMac, Mu,\n  Mupen64Plus-NX, Play, Potator, Reicast, Sudachi, VecX, Yabause, fuse, opera,\n  pcsx_rearmed, sm64ex, snesticle, supergrafx. Their games run on the thin\n  wrapper through the replacement table in `PVCore.swift` where a replacement exists (17\n  entries); cores that were disabled or never shipped have no entry.\n- **Rule of thumb:** if a libretro buildbot dylib already serves the\n  core and the thin wrapper handles it, fix the thin wrapper (or the\n  upstream dylib via Provenance fork), not a native PV* shell.\n  The whole point of the thin wrapper is to make those cores feel\n  basically native without per-core PV* maintenance.\n\n**Wrapper platform defaults — patch the right file:**\n- **Both iOS and tvOS** now default to the THIN libretro wrapper\n  (`PVCoreBridgeRetro/Sources/PVLibRetro/PVThinLibretroFrontend.mm`\n  + `PVThinLibretroCore*.swift`). dlopen + function pointers against\n  the libretro buildbot dylibs; no full RetroArch runtime.\n- **PPSSPP runs on the THIN wrapper, on Vulkan** (no force-route since `2f09fd7802`, 2026-05-28). On GLES, PPSSPP spawns its own render thread (`useEmuThread = GPUCORE_GLES`) that never has a current EAGL context, so GL can't work on thin; `GET_PREFERRED_HW_RENDER` returns Vulkan for PSP and GL is rejected (`e66b2a28a4`, `79a091c4b2`), with `context_reset` deferred until after `retro_load_game`. A failed deferred Vulkan setup now fails the load, and fence waits are bounded (`f87b9d17e1`).\n- **Thin `rendersToOpenGL` is true for every HW-render core, Vulkan included.** `PVThinLibretroFrontend` returns `_hwRenderRequested`, so a Vulkan core takes the \"OpenGL\" branch of `PVMetalViewController.draw(in:)` and never reaches the software-upload path. `rendersToVulkan` is the one that distinguishes them.\n- **Thin core options are replayed at boot, not read on demand.** The frontend holds option values only in memory; `PVThinLibretroCore.applyPersistedCoreOptions()` pushes the saved ones in from `afterCoreInitBlock` (after `retro_init`, before `retro_load_game`). The UI stores a switch state or a choice *label*; `ThinCoreOptionDefinition.rawValue(forStored:)` maps it to the core's value. A replay that never finishes booting is skipped once on the next launch.\n- The thick RetroArch wrapper (full in-process RetroArch, `PVRetroArch.framework`) has been removed; every libretro core runs on thin. What remains under `CoresRetro/RetroArch/` serves thin: `Core.plist` (the libretro core list, bundled via the `PVCoreLoader` resource symlink `RetroArchCore.plist`), `scripts/` (dylib download/packaging, `cores.yml`), `modules/` and `libretro-super`. A core from that list is available (`PVCore.hasCoreClass`) only when its `<name>.libretro.framework` is in the app bundle. Its top-level `com.provenance.core.retroarch` entry (the old launcher) is `PVDisabled`; its `PVRetroArch.PVRetroArchCoreCore` principleClass strings stay because `PVCoreFactory` keys on them.\n- **Thin netplay is libretro netpacket (env 78) only** — cores that register it (gpSP, melonDS DS) get netplay; RetroArch rollback netplay went with the RetroArch runtime. `NetpacketTransport` (PVNetplay) carries packets over framed TCP; `PVThinLibretroFrontend` hands every netpacket callback to the core on the emulation thread from a per-frame pump before `retro_run`, never from the network queue. Hosts advertise Bonjour `_provenance-np._tcp`, which must stay listed in every app Info.plist's `NSBonjourServices`. Cores with their own netplay socket (Dolphin ENet, mGBA, Mednafen, PPSSPP ad hoc) set `netplayAdvertisesOwnRoom = false` (the default) and `PVNetplayManager.host(settings:game:)` publishes the room for them (`NetServiceRoomAdvertiser`); TXT keys live in `NetplayRoomTXTRecord`. Without that, a LAN room is invisible to Browse.\n- **Thin wrapper gaps:** deferred Vulkan context setup (GL is now eagerly activated; Vulkan still deferred). BIOS files synced from `BIOSPath` into system directory before `retro_load_game`. `ScalingMode` integration works (via `PVMetalViewController` + DeltaSkin skin container). Fast-forward wired (`setGameSpeed:` override syncs `_speedMultiplier` + `_audioPaused`).\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access (see DEVELOPER.md for patterns)\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying third-party upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n### Build & toolchain gotchas\n\n- **Most workspace `.xcodeproj`s list sources explicitly** (e.g. the remaining native core projects), so a new file compiles in SwiftPM yet breaks the archive build. `Scripts/audits/check_pbxproj_sources.py` (CI: `xcode-project-sources.yml`) flags a source file sitting beside compiled ones that its project doesn't reference.\n- **Every script under `Scripts/` or `CoresRetro/RetroArch/scripts/` must be in `Scripts/maint/jobs.toml`** (as a job, or under `[ignore]`); `maint.yml` fails PRs that add an unregistered one. `make maint` (dashboard) / `python3 Scripts/maint/maint.py status` show which generated files are stale. Scripts that find the repo root from their own path sit two levels deep (`Scripts/<category>/`).\n- **PVUI cannot build inside a git worktree.** The PackageBuildInfo plugin reads `.git/HEAD`; a worktree's `.git` is a file. Run PVUI tests from an rsync'd copy with a fake `.git/HEAD` and submodule symlinks (see the Phase 1 overlay plan, Global Constraints).\n- **`gh issue list` has no `--sort` flag.** Use `gh issue list --search \"sort:created-desc\"` or `gh issue list --json number,title,createdAt --jq '.'` for sorted/filtered queries.\n\n### Dev workspace (Tuist)\n\nA second, generated workspace for fast iteration. `Provenance.xcodeproj` still ships\n(CI, fastlane, release.sh). Spec: `docs/superpowers/specs/2026-10-10-dev-workspace-design.md`.\n\n- **Generate / open:** Tuist 4.200.0 is pinned in `.mise.toml`. Run `mise exec -- tuist generate --no-open`\n  (or `make dev-generate`) from the repo root; `make dev` also opens `Provenance-Dev.xcworkspace`.\n  Generated files (`Provenance-Dev.xcworkspace`, `Dev/Provenance.xcodeproj`, `Dev/Derived`) are\n  gitignored. Never commit them. The Tuist project is named `Provenance` and lives in `Dev/`\n  because `Build.xcconfig` derives bundle ids, the app group and the iCloud container from\n  `$(PROJECT_NAME:lower)`.\n- **Focused apps:**\n  - `Provenance-Dev-UI`: mGBA, Stella, snes9x.\n  - `Provenance-Dev-Thin`: no native cores; 4 libretro dylibs (mednafen_psx_hw, mupen64plus_next, snes9x, ppsspp).\n  - `Provenance-Dev-Azahar`: Azahar.\n- `Provenance-Dev-EmuThree` (opt-in, iOS device): `make dev-emuthree` uses the same\n  `PVEmuThree-Dynamic` SwiftPM product as shipping. The package compiles the legacy engine\n  and bridge sources and selects the arm64 iOS/tvOS device or simulator vendor slice;\n  no core Xcode project is needed. Rebuild those slices with\n  `python3 Scripts/cores/build_emuthree_vendors.py`.\n  - `Provenance-Dev-Dolphin` (opt-in, device only): `Cores/Dolphin/Package.swift` wraps\n    `PVlibDolphin.xcframework` as a `.binaryTarget`. SwiftPM will not load the package until\n    `build_slice.py dolphin <slice>` has produced the xcframework, so `make dev-dolphin` runs it first\n    and sets `TUIST_DEV_DOLPHIN=1`; without that variable the app and package are not in the generated\n    project (CI never sets it). The ObjC++ target reaches the Swift module through the runtime\n    (`#if SWIFT_PACKAGE` in `PVDolphinCore.mm`), because SwiftPM can't build the mixed target as one.\n\n  Build with `make dev-ui|dev-azahar|dev-thin` (derived data in `build/dev-dd`; override with\n  `DEV_DERIVED=`). Each app is one iOS+tvOS target with bundle id\n  `org.provenance-emu.provenance.dev.<slug>`.\n- **Simulator builds are ad-hoc signed by `Dev/Config/Dev.xcconfig`.** Never pass\n  `CODE_SIGNING_ALLOWED=NO` to a dev build: the app then dies in dyld on the unsigned embedded\n  MoltenVK.\n- **Dev apps are arm64-only; Intel is unsupported** (decided 2026-10-12: no Intel Mac runs a\n  supported Xcode). `DevSettings` sets `EXCLUDED_ARCHS[sdk=*simulator*] = x86_64` for every focused\n  app, and the Makefile, harness and both CI workflows also pass `EXCLUDED_ARCHS=x86_64` to\n  xcodebuild, because a project setting never reaches package targets (CI kept compiling x86_64\n  for every package without it). Prebuilt cores need no x86_64 slice and SwiftPM core targets need\n  no per-arch source lists. Don't add x86_64 workarounds (stubs, generic-source fallbacks) here.\n- **The dev workspace has no committed Package.resolved, so it resolves the newest remote\n  packages.** Anything that only works because `Provenance.xcworkspace`'s resolved file pins an\n  older version breaks here first. Known case: Hummingbird imports `ServiceContextModule` without\n  declaring it, and swift-service-context 1.4.0 / swift-distributed-tracing 1.6.0 (2026-10-12)\n  dropped it from `Tracing`'s dependency chain, so builds failed at random with \"no such module\n  'ServiceContextModule'\"; `PVWebServer/Package.swift` caps both packages. Fix such breaks with a\n  version cap in the declaring package, not with xcodebuild flags.\n- **Helpers:** `Tuist/ProjectDescriptionHelpers/` holds `CoreProduct` (the core table),\n  `FocusedApp` (the target template plus `FocusedApp.all`), `LibretroCores` and `DevSettings`. Tuist\n  only finds helpers at `<root>/Tuist/ProjectDescriptionHelpers`; don't add a `Dev/Tuist/`.\n- **How cores get in:**\n  - SwiftPM dynamic cores are embedded with `.package(product:, type: .runtimeEmbedded)`.\n  - Cores from a hand-maintained `Cores/<X>/*.xcodeproj` are linked by `-framework <Product>` in `OTHER_LDFLAGS`.\n    That project is referenced from `Workspace.swift` (Xcode then builds it as an implicit dependency),\n    and the \"Embed core frameworks\" post script copies and signs the framework. Tuist can't\n    depend on targets of projects it doesn't generate.\n- **Libretro dylibs per app:**\n  - `FocusedApp.libretro` names (cores.yml) become a pre script calling\n    `CoresRetro/RetroArch/scripts/get-modules.sh --urls <file>`, plus a post script running\n    `make_frameworks_retroarch.sh` and `validate_frameworks.sh`.\n  - `--urls` keeps its own state dir (`modules_compressed/<iOS|tvOS>-urls-<sha12>`) and never prunes the shared `modules/`.\n  - The buildbot dylibs are iOS-platform binaries and cannot `dlopen` in a simulator, so\n    **Provenance-Dev-Thin plays games on a device only**.\n- **Manifest checks:** `Scripts/dev/check_dev_manifest.sh` compiles the helpers against Tuist's\n  `ProjectDescription.framework` and checks three things: every libretro name is enabled in cores.yml,\n  every path exists, and no local package is declared twice. Prebuilt artefacts (e.g. `PVlibDolphin.xcframework`) are\n  required only for rows a `FocusedApp` uses.\n- **Harness:** `make dev-harness ROM=<path> TARGET=ui|azahar FRAMES=n CORE=<id> SIM_DEVICE=<udid|booted>`\n  builds the app, launches it with the `-PVHarnessROM`, `-PVHarnessCore`, `-PVHarnessFrames`,\n  `-PVHarnessOut` and `-PVHarnessExit` launch arguments, and copies `screenshot.png`, `frames.json`, `log.txt` (or\n  `error.txt`) to `build/harness/<Scheme>/`. `TARGET=thin` is refused (exit 2): Thin runs on a device only.\n  Synthetic test ROMs come from `Scripts/dev/make_harness_rom.py` (2600 and gba). Frame counts are\n  time-based estimates (`\"frameCountSource\": \"estimated\"`). Its first catch was a native Stella VFS\n  regression (`16da1e4c40`).\n- **Core slices:** `Scripts/cores/build_slice.py <azahar|dolphin> <ios|ios-sim|tvos|tvos-sim>\n  [--print-key|--force|--xcframework|--cache-dir DIR]` builds or links one slice.\n  - The key is a content hash: submodule working tree (dirty edits bust the cache; `--force` rebuilds) and\n    nested externals, build script and toolchain, flags, Xcode and SDK, and Azahar's MoltenVK slice.\n  - Dolphin keys only on the dolphin-ios paths its CMake build reads (`DOLPHIN_BUILD_PATHS`: top-level\n    CMake files, `Externals`, `Source/Core`, `Source/iOS/Library`, `ApprovedInis.json`), as HEAD tree\n    hashes plus dirty edits under them. iCube app commits and edits there no longer force a rebuild;\n    if the build starts reading a new path, add it to that list.\n  - The cache is `$PV_CORE_CACHE` or `~/Library/Caches/Provenance/cores/<core>/<slice>/<key12>/`.\n  - On a hit, the legacy `build/xcframework/<Product>-<slice>.framework` path is a symlink into it.\n  - Only `--xcframework` repacks the multi-slice xcframework (Dolphin's is repacked after every miss).\n    If its hard links fail, set `PV_CORE_CACHE` to a folder on the repo's volume.\n- **CI:** `build.yml` and `testflight.yml` cache `~/Library/Caches/Provenance/cores` per platform,\n  keyed on `build_slice.py --print-key`. `dev-workspace.yml` smoke-builds `Provenance-Dev-UI` on\n  both simulators (Tuist via mise, which needs `GITHUB_TOKEN`).\n- **Pruning rule:** no new `Cores/` project without an audit row in\n  `docs/superpowers/specs/2026-10-10-core-audit.md`.\n\n### Azahar core build gotchas\n\n- **Shipping Xcode and Tuist consume `PVAzahar-Dynamic` from `Cores/Azahar/Package.swift`.** Prepare its cached CMake-built `PVlibAzahar.xcframework` before package resolution: `make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim`). `make open`, dev generation, CI and release entry points do this automatically. A cold slice takes ~30–40 min and needs cmake, ninja and python3 ≥ 3.10. The content key includes the submodule/externals, builder, toolchain, Xcode/SDK and MoltenVK slice; a hit reuses native output and refreshes `build/headers`. Force a rebuild with `build_slice.py azahar <slice> --force --xcframework`.\n- **The package graph always declares the native binary.** Missing output must be prepared before resolving; a build phase cannot produce an XCFramework early enough. The package's ObjC++ bridge (`PVAzaharBridge`) must never import the Swift module (`PVAzahar-Swift.h`): that is a target cycle. The Swift core pushes `renderViewClass` and `userDirectoryPath` into it from `init`, and runs its per-load setup (options, `respectsMuteSwitch`, emuThreeDS import) from the bridge's `willLoadFile` hook. A `loadFile` override on a Swift core never runs: `PVEmulatorViewController.createEmulator` calls `bridge.loadFile` for every `ObjCBridgedCore` (and `PVEmulatorCore.loadFile` only throws), which is how 3.4 TestFlight shipped \"Azahar has no user directory\".\n- **PVlibAzahar is arm64-only, and so is every simulator build.** `PVAzahar` excludes x86_64 simulator slices, as do `Build.xcconfig` (the shipping app) and the dev apps; the old link-only x86_64 stub is gone. Package targets ignore project settings, so generic-simulator builds also pass `EXCLUDED_ARCHS=x86_64` to xcodebuild.\n- **Never include azahar's `core/hle/service/nwm/nwm_uds.h`.** Azahar headers come in with `-idirafter`, so its `#include \"network/network.h\"` resolves to the SDK's Network framework header instead. The bridge includes azahar's own copy as `\"network.h\"` via a narrow `HEADER_SEARCH_PATHS` entry.\n- **Emulator changes go on the fork.** Commit them to `Provenance-Emu/azahar` branch `provenance` and list them in `Cores/Azahar/PATCHES.md`. Never edit `Cores/Azahar/azahar` in place.\n- **The tvOS user root is Caches.** `PVAzaharCore.userRootURL` is `Documents/Azahar` on iOS and `Library/Caches/Azahar` on tvOS, where Documents is not writable.\n- **`-fcxx-modules` is on** so the ObjC++ glue can `@import` its PV* dependencies. It must not import `PVAzahar-Swift.h`.\n- **`runOnEmuThreadAndWait:` jobs can outlive their timeout.** Capture only by value or `shared_ptr`, never by reference.\n- **PVAzahar links with `-unexported_symbols_list unexported_symbols.txt` (`__Z*`).** dyld coalesces *exported* weak C++ definitions (template instantiations, inline functions) across every loaded image. PVlibDolphin loads first and exports its own glslang and fmt instantiations from different library versions, and azahar's shader compiler crashed in glslang's parser after calling Dolphin's `addSwizzle`. Keep the C++ surface private; apply the same to any new core framework that static-links glslang, fmt, SPIRV-Cross or similar. PVEmuThree uses the same list. PVlibDolphin cannot (the PVDolphin bridge imports Dolphin's C++ API, including weak `MMU::HostRead<>` instantiations), so dolphin-ios's `BuildiOSXCFramework.py` relinks a second time with a generated list of third-party weak symbols via `-DDOLPHIN_UNEXPORTED_SYMBOLS_LIST`; the global `CMAKE_SHARED_LINKER_FLAGS` never reaches the `dolphin` target. Audit a built app with `nm -m <fw> | grep 'weak external'` per framework and `comm -12` the sorted lists.\n- **Keep C++ visibility filtering at the final framework link.** Compile-time hidden visibility lets Xcode's SwiftPM `ld -r` step localize bridge definitions before the Swift module and engine can resolve them. `Package.swift` forwards `-ObjC` with `-Xlinker` for the Swift link driver; `unexported_symbols.txt` hides C++ symbols in the final image.\n\n### Metal rendering gotchas\n\n- **`CAMetalLayer.nextDrawable` blocks forever when backgrounded.** Metal reclaims drawables during background. The CA runloop observer fires `MTKView.draw(in:)` before `didBecomeActiveNotification`, so `currentDrawable` deadlocks the main thread. ALL `draw(in:)` implementations MUST early-return when `UIApplication.shared.applicationState != .active`.\n- **`@synchronized(self)` in emulation loop vs main thread.** The emu loop holds `@synchronized(self)` around `executeFrame` at 60fps. Any main-thread code that acquires the same lock (e.g., `setPauseEmulation`) will starve. Set atomic flags BEFORE acquiring the lock so the emu loop sees the change and releases.\n- **Thin wrapper reports its geometry/aspect/sample-rate LATE.** `PVThinLibretroFrontend` populates `_rawAVInfo` (geometry, aspect, `timing.sample_rate`, fps) only at the END of `startWithROMPath` — which runs inside `startEmulation`, NOT during `loadFile` (that only dlopens the core). But `createEmulator()` sets up the audio graph (`setupAudioGraph`/`startAudio`) AND the DeltaSkin viewport BEFORE `core.startEmulation()`. So anything that reads the core's geometry/aspect/sample-rate at view/audio-setup time gets the fallback defaults (`?: 640/480/256`, `audioSampleRate → 44100`), not the real values. Native/thick cores populate av_info during `loadFile`, so they don't hit this. This has caused: (1) thin-wrapper audio built at 44100 then never rebuilt → bassy/glitchy (fixed by rebuilding the audio graph after start if the rate changed, #cb40fef061); (2) skin render view stuck full-screen/wrong-size until rotation because the viewport was computed before geometry settled (fixed by recomputing on layout-settle, #a63e1622f1). Pattern for any new feature that reads core geometry/rate: recompute/refresh AFTER `startEmulation`, gated on the value actually having changed.\n\n### Shader / filter system\n\n- **The slang library is fetched at build time, never committed.** `Scripts/shaders/fetch_slang_shaders.py` downloads the release pinned in `Scripts/shaders/slang-shaders.lock.json` (JoeMatt/slang-shaders-metal, `.oecompiledshader` zips compiled from libretro slang by OpenEmu-Shaders' `oeshaders`), unpacks each preset into a `shader.json` directory (no runtime unzip), merges the hand-written presets in `PVShaders/Library/native-metal/`, and writes `manifest.json`. Every app target (shipping xcodeproj and Tuist `FocusedApp`, `slangShaders:`) copies it into the bundle as `SlangShaders/` via `embed_slang_shaders.sh`. To bump the snapshot: change the lock, run `Scripts/shaders/validate_slang_shaders.sh` (renders every preset on the Mac GPU, writes `slang-shaders-excluded.json`), commit both.\n- **Which filter draws is per game.** `ShaderLibraryStore` (PVShaders) holds selections for global / `system:<id>` / `game:<md5>` scopes (game wins), parameter overrides, favorites and recents; selections, parameters and favorites sync through iCloud KVS as timestamped per-key maps (`StampedMap`, merged both ways via `iCloudSettingsSync.register`). Native filters keep their values in their old `Defaults` keys. The legacy `metalFilterMode` key is still bridged into the global selection; its raw values are case names (`Always(vhs)`, not `Always(VHS)`).\n- **`ShaderSelection.off`, not `.none`.** A `.none` case on an enum passed as `ShaderSelection?` silently becomes `Optional.none` and clears the scope.\n- **Renderers:** `PVMetalViewController` resolves the filter every frame (native → `PVMetalFilterRenderer`, library → `SlangFilterRenderer`, which compiles presets off the main thread and draws the plain picture until ready). Cores that present from their own thread (Dolphin Metal backend, native PPSSPP) use `ShaderPostProcessor`, fed a resolved snapshot on `ShaderLibraryStore.didChangeNotification`. Dual-screen layouts and Azahar are never filtered (pause tiles hidden via `filtersApplyToCurrentLayout`); `LCDScreenFilter` (CIFilter) is the intended DS/3DS look.\n- **Filters draw into the picture rect, not the drawable.** Until a skin reports its screen rect the GPU view is full screen; `RenderGeometry.contentRect` letterboxes the picture inside it. Code that draws over the frame must use the same rect (this was the \"VHS covers the whole screen\" bug).\n- **`swift test` in PVShaders runs real Metal tests on the host GPU**, but the SwiftPM CLI only copies `.metal` files: the slang runtime compiles its bundled source as a fallback, while native-filter pipelines (package metallib) exist only in Xcode builds, so those tests skip.\n\n### Lookup databases (PVLookup)\n\n- **The databases are fetched at build time, never committed.** `Scripts/lookup/databases.lock.json` pins five assets by sha256 on a `databases-YYYY-MM-DD` release of Provenance-Emu/PVLookup. Every app target (shipping xcodeproj \"Script: Embed Lookup Databases\", Tuist `DevSettings.embedLookupDatabasesScript`) runs `embed_lookup_databases.sh`, which fetches them into the gitignored `LookupDatabases/` (cache: `~/Library/Caches/PVLookupDatabases`) and copies the zips to the bundle ROOT, where PVLookup's default `LookupDatabases` source looks for `<file name>.zip` in `Bundle.main`. No `LookupDatabases.configure` call is needed. `make lookup-databases` fetches by hand.\n- **`fetch-lookup-databases.sh` is a copy of PVLookup's canonical script**; keep it identical. To bump the databases: publish a new release in the PVLookup repo, then update the lock file.\n- **The cheat database generator still lives here.** `make update-cheatdb` writes `build/cheatdb/libretro_cheats.sqlite.zip` for publishing as a PVLookup release asset; the app never builds from it.\n- **PVLookup logs through `PVLookupLog`**, routed into PVLogging by `LoggingBootstrapTask` on a serial queue: PVLogging's `log()` holds four `Logger` interpolations, and calling it at the bottom of a deep lookup overflowed a 512 KB concurrency stack in a Debug build (iFly).\n- **`PVLookupTypes` and `TheGamesDB` are not products.** Their imports in PVLibrary/PVUI resolve through the `PVLookup` product's transitive modules, as they always did.\n\n### Debugging emulator cores\n\n- **\"Black screen\" reports: ask for the `[NO-FRAME]` log line.** Five seconds after a successful boot with nothing presented, `PVEmulatorViewController+FirstFrameWatchdog` logs the presenter, render path, pause state, geometry and the GPU view's frame/visibility. Cores that draw to their own view (native Dolphin, PPSSPP, RetroArch `skipLayout`) trip it by design.\n- **Per-game core options depend on `CoreOptionsContext.currentGameMD5`.** It is set in `initCore()` before the core initializes and cleared in `quit`; `CoreOptional.currentGameMD5` defaults to it. Without it a core reads only the core-wide key and per-game overrides are silently ignored.\n- **flycast cannot be debugged with Xcode attached.** It installs `signal(SIGSEGV, ...)` for VRAM lazy-mapping; Xcode catches SIGSEGV and pauses, breaking the core. Use Console.app (filter `Process = Provenance`) for live logs OR `iOS Settings → Privacy & Security → Analytics → Analytics Data` for `.ips` crash files post-mortem.\n- **Sentry's `enableCrashHandler` is disabled** at `SentryBootstrapTask.swift:48` because its SIGSEGV handler conflicts with flycast's MMU path. Do NOT re-enable it. Crash telemetry flows through MetricKit instead.\n- **iPad MoltenVK surface_caps lie.** On iPadOS 26 with Stage Manager / adaptive scaling, `VkSurfaceCapabilitiesKHR.currentExtent` / `minImageExtent` / `maxImageExtent` ALL report `view.bounds × contentScaleFactor`, NOT the actual `CAMetalLayer.drawableSize`. They can differ (e.g. 2732×2048 vs 2092×1568). The authoritative source for iOS Vulkan is `metalLayer.drawableSize`. Never clamp against MoltenVK surface caps on iOS.\n- **libretro core option key prefixes don't always match the core name.** flycast's options are `reicast_*` (per `CORE_OPTION_NAME` in `shell/libretro/libretro_core_option_defines.h`). Always grep the core's `libretro_core_options.h` before guessing key names.\n- **Jaguar numpad uses keyboard input.** The upstream `virtualjaguar-libretro` core reads numpad buttons 0-9/*/# via `RETRO_DEVICE_KEYBOARD` (`RETROK_0`-`RETROK_9`, `RETROK_MINUS`, `RETROK_EQUALS`), NOT joypad buttons. Buttons 7-9/*/# have NO joypad mapping. The thin wrapper dispatches both keyboard events (via `_bridge.setKeyState`) and joypad presses for compatibility.\n\n### Code conventions\n\n- **Library storage: `LibraryLocation` owns the roots; never move library folders by hand.** `URL.documentsPath`/`cachesPath` and `LibraryLocation.current` share one launch-fixed precedence (stored `libraryLocation` → legacy `useAppGroups`, both snapshotted at launch). Flipping `useAppGroups` alone no longer moves files.\n  - Moves go through `LibraryMoveCoordinator` (PVFileSystem `LibraryMove/`): journaled in the app's Application Support, verified (size; MD5 under `ROMs/`+`BIOS/`), switched by writing the setting, and cleaned up on the *next* launch after a delta sweep.\n  - Leaving the App Group moves every top-level entry except hidden files, `.realm` names and the journal, including entries created after the move started. \"Identical\" means size **and** MD5, in every folder. A copy-phase conflict moves the destination's own file aside to `Conflicts/` and puts the live file at the real path; the post-switch sweep files a changed source under `Conflicts/`. Nothing is ever deleted or overwritten, and copies go through a hidden temp file and a rename.\n  - \"RESET TO DEFAULT\" must not touch `libraryLocation`/`useAppGroups`. Lite and the full app share the group, so a move keeps a copy while the other app still uses it.\n  - A folder picked in Files is `LibraryLocation.pickedFolder`: the security-scoped bookmark is `Defaults[.libraryPickedFolder]`, resolved once per launch by `LibraryLocation.pickedFolderAtLaunch` (never stopped; a failure is `.unavailable` and the UI offers re-pick / App storage / retry). It means two roots: `rootURL` for `ROMs/`, `BIOS/`, `System/` and `savesRootURL` for the five saves folders, which join the picked folder only when `includesSaves` is on. `LibraryFolderInspector` gates every pick (writable, not the library itself, not inside a moved folder). The journal is version 2 (`sourceFolder`, `destinationFolder`, `deduplicateByMD5`, `remaps`).\n  - Joining a folder that already has `.provenance-library.json` de-duplicates `ROMs/` and `BIOS/` by size + MD5 under any name; the resulting `remaps` must be applied to the database before cleanup deletes sources, so a remap applier (`LibraryPathRemapper`) has to be registered before `resumeIfNeeded()` (`PVGameLibrary.init` does this). iCloud-located picked folders are watched by `PickedFolderCloudWatcher`.\n- **Changing a game's MD5 goes through `GameRekey`, never a delete.** `PVGame.md5Hash` is the Realm primary key, so a ROM rewritten in place (\"Replace the Original\") needs a new row: `GameRekey.rekey(gameWithMD5:to:crc:fileSize:in:)` copies the game (keeping `id`), re-points saves, cheats, recents, patches, libraries and controller profiles, then raw-deletes the old row. `RomDatabase.delete(game:)` would delete the ROM file, saves and patches. The library MD5 is the importer's (`GameImporterDatabaseService.calculateMD5(forGame:)`, header-aware), not the raw file MD5. Replacing is blocked while iCloud Sync is on: another device's soft-delete of the old game removes the file at the shared path.\n- **Notification names — no magic strings.** Declare `FOUNDATION_EXPORT NSNotificationName const FooBarNotification;` in an ObjC `.h`, define in the matching `.mm`, mirror via `Notification.Name` extension in Swift referencing the same string. Posters reference the const; observers reference `Notification.Name.fooBar`. Single string literal lives in one `.mm` — never at a call site.\n- **Email in public-facing files / comments:** use `git@joemattiello.com` (SECURITY.md, README, GitHub issue comments, anything that ships publicly).\n- **ObjC categories in SwiftPM modules can be silently elided.** When a category is declared in a separate `.h` file referenced by a modulemap, Swift module synthesis may drop it entirely — no error, just \"no member\" at call sites. Workaround: declare methods in the main `@interface` block. Known to affect `(LightGun)` categories on PVStellaBridge and PVProSystemGameCore. Other categories (Cheats, Trackball) in the same files work fine — root cause unclear.\n\n### Subagent worktree rules\n\n- Subagents in `isolation: worktree` MUST stay on their own branch — never `git reset` / `rebase` / `push` / touch `develop`. Their commits get cherry-picked onto develop afterward.\n- Always include \"DO NOT git reset / rebase / push / touch develop\" in subagent prompts. Without it, parallel agents have historically collided (one agent ran `git reset HEAD~1` and dropped another agent's commit).\n- After an agent reports completion: cherry-pick from the agent's branch onto develop in the MAIN worktree's `Provenance/` directory. Shell can land in the agent's worktree if you're not careful — verify `pwd` and `git branch --show-current` before each cherry-pick.\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# PVUI unit tests (PVUIBaseTests + PVSwiftUITests). Use the committed\n# PVUI-UnitTests scheme, not the auto-generated PVUI one: that also builds the\n# snapshot suite, whose Prefire-generated code doesn't compile. On Xcode 26.6\n# realm-core needs `-xcconfig` with OTHER_CFLAGS/OTHER_CPLUSPLUSFLAGS =\n# $(inherited) -Wno-invalid-specialization (CI's Xcode 26.3 doesn't).\ncd PVUI && xcodebuild test -scheme PVUI-UnitTests \\\n  -destination 'platform=iOS Simulator,name=iPhone 17' \\\n  CODE_SIGNING_ALLOWED=NO -skipPackagePluginValidation -skipMacroValidation\n\n# PVLibrary tests: the committed PVLibrary-UnitTests scheme, through the\n# package workspace (PVLibrary/PVLibrary.xcodeproj shadows Package.swift, and\n# the auto-generated scheme has no test action). Same realm-core -xcconfig on\n# Xcode 26.6. CI: pvlibrary-tests.yml (develop pushes + PRs via agent-validation).\ncd PVLibrary && xcodebuild test -workspace .swiftpm/xcode/package.xcworkspace \\\n  -scheme PVLibrary-UnitTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  -collect-test-diagnostics never CODE_SIGNING_ALLOWED=NO \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# PVCoreBridgeRetro (thin wrapper) tests: PVLibRetroTests in Package.swift.\n# Open the package in Xcode and select its test scheme, or use its package workspace.\n# Ad-hoc signing is required for MoltenVK.\nxcodebuild test -workspace PVCoreBridgeRetro/.swiftpm/xcode/package.xcworkspace -scheme PVCoreBridgeRetro \\\n  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  CODE_SIGN_IDENTITY=- CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM= \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Agents should scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode (PVShaders: `swift test` works) |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **GitHub workflow files** — `.github/workflows/*.yml` may be edited when fixing CI issues or optimizing builds. However, GitHub Actions bot PRs cannot push workflow changes (requires `workflows` permission) — if an agent PR needs to include workflow changes, note this in the PR description so a maintainer can push them manually.\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are third-party upstream code (do not casually fork in place)\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **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. Use `PBXFileSystemSynchronizedRootGroup` for source directories (Xcode 16+). Prefer minimal diffs — only touch the sections that need changing.\n\n### Minimum Deployment Targets\n\nProvenance ships for **iOS 17+ and tvOS 17+**. The PV* packages also declare **macOS 14+ and visionOS 1+** so keep code compiling for them with platform guards, but no Catalyst/native-mac/visionOS app target ships today (native macOS is planned — see the 2026-08-07 macOS strategy spec). 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.\n\n**Prefer modern Swift/SwiftUI APIs** when the minimum deployment target supports them:\n\n| Prefer (iOS 16+/17+) | Over (older) |\n|---|---|\n| `ShareLink` | `UIActivityViewController` wrapped in `UIViewControllerRepresentable` |\n| `@Observable` macro (iOS 17+) | `@ObservableObject` + `@Published` |\n| `NavigationStack` | `NavigationView` |\n| `.navigationBarTitleDisplayMode(.inline)` on non-tvOS | conditional guard |\n| `UIWindowScene.keyWindow` | `UIApplication.shared.keyWindow` (deprecated iOS 13) |\n\n**`@StateObject` vs `@ObservedObject` rule:**\n- `@StateObject` — use only when the view **creates and owns** the object's lifetime (new instances).\n- `@ObservedObject` — use for **singletons** (e.g. `Foo.shared`) and objects passed in from outside.\n  Using `@StateObject` with a singleton is semantically wrong even though it compiles.\n\n### Pre-PR Validation (MANDATORY)\nAgents MUST run these checks before creating a PR. Do NOT skip any step.\n\n1. **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).\n\n2. **Lint** — Run `swiftlint lint --path <file>` on every changed Swift file.\n\n3. **Unit tests** — If the module has tests, run them: `cd PV<Module> && swift test`. If adding new logic, add test coverage.\n\n4. **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.\n\n5. **No magic numbers** — Extract constants. Don't hardcode values that are used in multiple places.\n\n6. **Use `SystemIdenti",".github/copilot-instructions.md":"# Copilot Instructions for Provenance\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.2 (`/Applications/Xcode_16.2.app`)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores\n- Each has iOS and tvOS variants\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVUI** — SwiftUI-based shared UI components\n- **PVSettings** — User preferences\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVSupport** — Shared utilities\n- **PVLogging** — Logging infrastructure (OSLog-based; use `DLOG`/`ILOG`/`ELOG`/`WLOG`)\n- **PVPrimitives** — Base data types shared across modules\n- **PVCheevos** — RetroAchievements integration\n- **PVHashing** — ROM file hashing for identification\n- **PVLookup** — Game metadata lookup (separate repo, github.com/Provenance-Emu/PVLookup; databases fetched at build time via `Scripts/lookup/databases.lock.json`)\n- **PVJIT** — JIT compilation support for emulator cores\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Code Style\n\n### Swift Code\n- Prefer `async/await` over Combine where possible\n- Use `@Sendable` and proper actor isolation for concurrency\n- Avoid force unwraps (`!`) in production code; use `guard let` or `if let`\n- Line length limit: 200 characters (`.swiftlint.yml`)\n- 4-space indentation, no indent for `case` in `switch`\n\n### Emulator Core Bridges (`*Bridge+Controls.mm`)\n- All controller types must be handled: Extended, Micro, Keyboard\n- Button mappings must match upstream emulator constants\n- Never modify upstream submodule source in `Cores/<name>/<upstream-dir>/`\n\n## Common Patterns\n\n### Logging\nUse PVLogging functions — never `print` or `NSLog`:\n```swift\nimport PVLogging\nDLOG(\"Debug: \\(value)\")    // Debug builds only\nILOG(\"Info: \\(message)\")   // Info\nWLOG(\"Warning: \\(msg)\")    // Warning\nELOG(\"Error: \\(error)\")    // Error\nVLOG(\"Verbose detail\")     // Debug builds only (verbose)\n```\n\n### Realm Threading\nRealm objects are thread-confined — never pass them across threads:\n```swift\n// Wrong — crashes across threads:\nlet game = realm.objects(PVGame.self).first!\nTask.detached { _ = game.title } // CRASH\n\n// Right — pass primary key and re-fetch on new thread:\nlet gameID = game.id\nTask.detached {\n    let realm = try await Realm()\n    let game = realm.object(ofType: PVGame.self, forPrimaryKey: gameID)\n}\n\n// Or freeze for read-only cross-thread access:\nlet frozenGame = game.freeze()\n```\n\n### Feature Flags\nGate new features behind `PVFeatureFlags` (Tier 0, importable anywhere):\n```swift\nimport PVFeatureFlags\n\n// Check at runtime (PVFeatureFlags is @MainActor):\nif await PVFeatureFlags.shared.isEnabled(.myFeature) { ... }\n\n// In SwiftUI views via environment:\n@Environment(\\.featureFlags) var featureFlags\nif featureFlags.myFeature { ... }\n```\n\n### Actor Isolation\nUI updates must happen on the main actor:\n```swift\n@MainActor\nclass MyViewModel: ObservableObject { ... }\n\n// From an async context:\nTask { @MainActor in\n    self.isLoading = false\n}\n```\n\n### Platform-Specific Code\n```swift\n#if os(tvOS)\n    // tvOS: no touch input; use Focus Engine and game controllers as primary input\n#elseif os(iOS)\n    // iOS: touch gestures + game controllers\n#endif\n```\n\n## Platform Support Matrix\n\n### Primary Targets (always must compile and work)\n- **iOS 17+** — minimum deployment target\n- **tvOS 17+** — minimum deployment target\n\n### Aspirational Targets (compile where possible, especially non-UI code)\n- **macOS 14+** (Sonoma) — Designed for iPad today; native target planned; Catalyst not supported\n- **watchOS 10+** — limited scope; non-UI utility code only\n- **visionOS 1+** — Aspirational — package declarations + #if os(visionOS) guards only; no app target\n\n### Unit Test Platform\n- **Linux** — Tier 0–2 modules should compile on Linux for CI unit tests via `swift test`\n- Avoid Darwin-only APIs in Tier 0–2 without `#if canImport(Darwin)` guards\n\n### Guards\n```swift\n#if os(iOS)\n    // iOS-only (touch, UIKit specifics)\n#elseif os(tvOS)\n    // tvOS-only (Focus Engine, no touch)\n#elseif os(macOS)\n    // Mac-only\n#elseif os(watchOS)\n    // watchOS-only\n#elseif os(visionOS)\n    // visionOS-only\n#endif\n```\n\n## GitHub Project & Issue Workflow\n\n### Project Board\nAll issues and PRs should be tracked on the [Provenance Roadmap](https://github.com/orgs/Provenance-Emu/projects/1/views/1).\n\n### Labels\nUse these labels consistently:\n- `bug` — regressions or broken behavior\n- `enhancement` — new features or improvements\n- `Epic` — parent issue grouping related sub-tasks\n- `agent-work` — issue assigned to Claude/AI agent lane\n- `cursor-work` — issue assigned to Cursor agent lane\n- `ai-reviewing` — PR currently being reviewed by AI\n- `ai-reviewed` — PR review complete\n- `needs-fixes` — review found issues requiring changes\n- `ready-for-review` — AI review passed, awaiting @JoeMatt\n\n### Epics & Sub-tickets\n- Epic issues use the `Epic` label and list sub-tasks as checkboxes in the body\n- Sub-tasks reference their parent epic with \"Part of #EPIC_NUMBER\" in the PR/issue body\n- Close sub-tasks by merging their PR; epic auto-closes when all checkboxes are checked\n\n### Tagging Reviewers\n- Tag **@JoeMatt** for final human review on any significant change\n- Tag **@copilot** in a PR comment to request a Copilot review pass\n- Tag **@claude** in a PR comment to request a Claude fix/review pass\n- Do NOT tag both simultaneously — one at a time\n\n### WhatsNew / Release Notes\nSee \"## WhatsNew & Release Versioning\" section below for rules.\n\n## WhatsNew & Release Versioning\n\n### CRITICAL: Never Guess Version Numbers\n\nThe `whats-new.json` file (`PVUI/Sources/PVSwiftUI/Resources/whats-new.json`) drives\nthe in-app \"What's New\" sheet. Agents MUST NOT add entries with speculative future version\nnumbers. **The current released version is the latest git tag** — check with:\n```bash\ngit tag --sort=-v:refname | head -1\n```\n\n### Rules for Agents\n1. **Only add a `whats-new.json` entry when**: the PR contains a major user-visible feature AND the target version has been explicitly confirmed in the issue/PR\n2. **Never invent version numbers** — if unsure, skip the entry and note it in the PR body with `TODO: add whats-new entry for version X.Y.Z`\n3. **Version format**: semver string matching the Xcode `MARKETING_VERSION` (e.g. `\"3.3.0\"`)\n4. **Max 6 features** per version entry\n5. **Do NOT modify existing entries** for already-shipped versions\n\n### Changelog Fragments\nPrefer adding `.changelog/<PR_NUMBER>.md` fragments over editing `CHANGELOG.md` directly.\nThe consolidation workflow (`consolidate-changelog.yml`) merges fragments on PR merge.\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are upstream code\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **project.pbxproj** — avoid when possible; prefer SPM Package.swift changes\n\n### What NOT to Flag in Code Review\n- `force_cast` and `force_try` are intentionally warnings, not errors\n- Generated files (`Version.h`, `Version.swift`) — skip review\n- Submodule contents (`Cores/<name>/<upstream>/`) — skip review\n- `project.pbxproj` changes — minimize review noise\n\n### PR Requirements\n- Target the `develop` branch\n- Include test coverage for new logic (where testable)\n- Keep scope focused — one logical change per PR\n- Run `swiftlint` on changed files before submitting\n- Agent PRs should use the `[Agent]` prefix in title\n\n### Branch Naming & Commit Messages\n- Branches: `agent/issue-<N>` for agent work, `feature/<description>` for features\n- Commits: Use conventional commits (`fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`)\n- Keep commit messages concise (< 72 chars for subject line)\n","AGENTS.md":"# AGENTS.md\n\n## Start here\n- Read `.cursorrules` and `CLAUDE.md` before making changes.\n- Keep scope to one logical change per task.\n- Target the `develop` branch and use conventional commits: `fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`.\n- Prefer the lowest dependency tier that can satisfy the task.\n\n## Project snapshot\n- Provenance is an iOS/tvOS emulator frontend built from many `PV*` Swift packages plus Objective-C / Objective-C++ bridge layers for emulator cores.\n- Main app targets live in `Provenance/`, `ProvenanceTV/`, and `Extensions/`.\n- Emulator bridge work usually lives under each core directory in `Cores/<CoreName>/` or in `CoresRetro/RetroArch/`.\n- Persistence uses Realm and CloudKit. Realm objects are thread-confined; pass object identifiers or frozen objects across threads.\n\n## Hard constraints\n- Do not modify upstream submodule source under `Cores/<name>/<upstream-dir>/`.\n- Do not modify `CodeSigning.xcconfig`.\n- Avoid `project.pbxproj` changes unless there is no viable package-based alternative.\n- Prefer `Package.swift` updates over Xcode project edits whenever possible.\n- Avoid touching generated files such as `Version.h`, `Version.swift`, or build output under generated directories.\n- Preserve existing comments and logic unless the task explicitly requires changing them.\n\n## Module tiers\n- Tier 0: `PVObjCUtils`, `PVFeatureFlags`, `PVCheevos`\n- Tier 1: `PVLogging`, `PVPlists`, `PVHashing`\n- Tier 2: `PVSettings`, `PVPrimitives`\n- Tier 3: `PVSupport`, `PVAudio`, `PVCoreAudio`\n- Tier 4: `PVCoreBridge`, `PVEmulatorCore`, `PVShaders`\n- Tier 5: `PVCoreBridgeRetro`, `PVCoreLoader`, `PVLibrary`\n- Tier 6: `PVUI`, app targets\n\n## Swift and platform conventions\n- Use 4-space indentation and keep lines within the repo's 200-character limit.\n- Prefer `async` / `await` for new asynchronous work.\n- Prefer `guard` / optional binding over force unwraps in production code.\n- When adding or changing Swift APIs, add concise `///` documentation comments where they help future readers.\n- Prefer protocol-oriented designs when they simplify the implementation.\n- Assume Swift changes must work for both iOS and tvOS. Guard unsupported APIs with conditional compilation.\n- Do not add comments that merely restate the code.\n\n## Validation guidance\n- For Tier 0-2 modules, validate locally with `swift build` or `swift test` inside the module when the change is testable.\n- Run `swiftlint lint --path <changed-swift-file>` for changed Swift files when `swiftlint` is available.\n- For higher-tier modules and app targets, validate from `Provenance.xcworkspace`.\n- Start with the `Provenance-Lite` scheme for the fastest Xcode validation. Lite is an internal test build (never published, no users): never release-gate on it or build Lite-only migrations.\n- If an initial Xcode build fails because generated sources are missing, retry once before investigating further.\n- Do not run full iOS/tvOS command-line builds unless the user explicitly asks for them.\n\n## Cursor Cloud specific instructions\n- Cursor Cloud agents here run on Linux, so Xcode, Simulator, and Apple platform runtime validation are unavailable.\n- In this environment, fully validate docs changes and any standalone Swift Package work that can run without Xcode.\n- For workspace-level iOS/tvOS changes that require Xcode, do the best possible static review, run any repo checks that are available, and clearly call out the Mac-only validation that still needs to happen.\n- Never claim an app runtime or simulator change is verified unless it was actually exercised on macOS with Xcode.\n\n## Quick commands\n- `swiftlint lint --path <file>`\n- `cd PV<Module> && swift build`\n- `cd PV<Module> && swift test`\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\n## Start here\n- Read `.cursorrules` and `CLAUDE.md` before making changes.\n- Keep scope to one logical change per task.\n- Target the `develop` branch and use conventional commits: `fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`.\n- Prefer the lowest dependency tier that can satisfy the task.\n\n## Project snapshot\n- Provenance is an iOS/tvOS emulator frontend built from many `PV*` Swift packages plus Objective-C / Objective-C++ bridge layers for emulator cores.\n- Main app targets live in `Provenance/`, `ProvenanceTV/`, and `Extensions/`.\n- Emulator bridge work usually lives under each core directory in `Cores/<CoreName>/` or in `CoresRetro/RetroArch/`.\n- Persistence uses Realm and CloudKit. Realm objects are thread-confined; pass object identifiers or frozen objects across threads.\n\n## Hard constraints\n- Do not modify upstream submodule source under `Cores/<name>/<upstream-dir>/`.\n- Do not modify `CodeSigning.xcconfig`.\n- Avoid `project.pbxproj` changes unless there is no viable package-based alternative.\n- Prefer `Package.swift` updates over Xcode project edits whenever possible.\n- Avoid touching generated files such as `Version.h`, `Version.swift`, or build output under generated directories.\n- Preserve existing comments and logic unless the task explicitly requires changing them.\n\n## Module tiers\n- Tier 0: `PVObjCUtils`, `PVFeatureFlags`, `PVCheevos`\n- Tier 1: `PVLogging`, `PVPlists`, `PVHashing`\n- Tier 2: `PVSettings`, `PVPrimitives`\n- Tier 3: `PVSupport`, `PVAudio`, `PVCoreAudio`\n- Tier 4: `PVCoreBridge`, `PVEmulatorCore`, `PVShaders`\n- Tier 5: `PVCoreBridgeRetro`, `PVCoreLoader`, `PVLibrary`\n- Tier 6: `PVUI`, app targets\n\n## Swift and platform conventions\n- Use 4-space indentation and keep lines within the repo's 200-character limit.\n- Prefer `async` / `await` for new asynchronous work.\n- Prefer `guard` / optional binding over force unwraps in production code.\n- When adding or changing Swift APIs, add concise `///` documentation comments where they help future readers.\n- Prefer protocol-oriented designs when they simplify the implementation.\n- Assume Swift changes must work for both iOS and tvOS. Guard unsupported APIs with conditional compilation.\n- Do not add comments that merely restate the code.\n\n## Validation guidance\n- For Tier 0-2 modules, validate locally with `swift build` or `swift test` inside the module when the change is testable.\n- Run `swiftlint lint --path <changed-swift-file>` for changed Swift files when `swiftlint` is available.\n- For higher-tier modules and app targets, validate from `Provenance.xcworkspace`.\n- Start with the `Provenance-Lite` scheme for the fastest Xcode validation. Lite is an internal test build (never published, no users): never release-gate on it or build Lite-only migrations.\n- If an initial Xcode build fails because generated sources are missing, retry once before investigating further.\n- Do not run full iOS/tvOS command-line builds unless the user explicitly asks for them.\n\n## Cursor Cloud specific instructions\n- Cursor Cloud agents here run on Linux, so Xcode, Simulator, and Apple platform runtime validation are unavailable.\n- In this environment, fully validate docs changes and any standalone Swift Package work that can run without Xcode.\n- For workspace-level iOS/tvOS changes that require Xcode, do the best possible static review, run any repo checks that are available, and clearly call out the Mac-only validation that still needs to happen.\n- Never claim an app runtime or simulator change is verified unless it was actually exercised on macOS with Xcode.\n\n## Quick commands\n- `swiftlint lint --path <file>`\n- `cd PV<Module> && swift build`\n- `cd PV<Module> && swift test`\n","isInternal":false,"tokens":879,"sizeBytes":3718},{"name":".cursorrules","path":".cursorrules","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.cursorrules","title":"Cursor IDE Native Rules","category":"cursor-rule","format":"markdown","content":"You are working on Provenance, a multi-platform emulator frontend for iOS/tvOS.\n\n## Key Rules\n- Read CLAUDE.md for full project conventions, module tiers, and architecture\n- Target the `develop` branch for all PRs\n- Use conventional commits: fix:, feat:, chore:, build:, refactor:, test:, docs:\n- Do NOT modify third-party submodule source in `Cores/<name>/<upstream-dir>/`\n- Do NOT modify project.pbxproj unless absolutely necessary\n- Do NOT modify CodeSigning.xcconfig\n- Prefer SPM Package.swift changes over Xcode project changes\n- Keep changes focused — one logical change per PR\n\n## Swift Conventions\n- 4-space indent, no indent for case in switch\n- Line length: 200 chars max\n- Prefer async/await over Combine for new code\n- Use guard let / if let, avoid force unwraps in production code\n- Realm objects are thread-confined — pass Object IDs across threads\n\n## Build\n- Open Provenance.xcworkspace (not .xcodeproj)\n- Start with Provenance-Lite scheme for fastest builds\n- Initial builds may fail due to lazy code generation — retry once\n- Never build iOS/tvOS from command line unless explicitly asked\n\n## Module Tiers (lowest = fewest dependencies)\n- Tier 0: PVObjCUtils, PVFeatureFlags, PVCheevos\n- Tier 1: PVLogging, PVPlists, PVHashing\n- Tier 2: PVSettings, PVPrimitives\n- Tier 3+: PVSupport, PVAudio, PVCoreBridge, etc.\n- Scope changes to the lowest tier possible\n\n## Testing\n- swift test in standalone SPM modules (Tier 0-2)\n- Xcode workspace build for higher-tier modules\n- Run swiftlint on changed files before submitting\n","isInternal":false,"tokens":388,"sizeBytes":1541},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.x or Xcode 26+ (use `xcode-select -p` to confirm active version; both are supported)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n- Minimum targets: iOS 17+, tvOS 17+ mandatory. macOS today = \"Designed for iPad\" (the iOS binary on Apple Silicon); a native macOS target is planned (see docs/superpowers/specs/2026-08-07-macos-visionos-strategy-design.md). Mac Catalyst is NOT supported. visionOS/watchOS/Linux: SPM packages declare them — keep code compiling with guards, but no app ships for them.\n\n### Code Signing\nCopy `CodeSigning.xcconfig.sample` to `CodeSigning.xcconfig` and fill in your developer account details.\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\n**Azahar native artifact preparation:** before opening or resolving the shipping workspace, run\n`make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim` for the intended destination).\n`make open` prepares iOS by default. The command reuses the native slice cache and packs its XCFramework;\nSwiftPM owns the wrapper, bridge, Core.plist and migrator test targets. Tuist's `make dev-azahar` performs\nthe same preparation through `dev-generate`. Native engine changes require preparing again before building.\nArm64 simulator builds require `ARCHS=arm64` or `EXCLUDED_ARCHS=x86_64` at the build invocation level.\n\n**Dolphin native artifact preparation:** run `make prepare-dolphin DOLPHIN_SLICE=ios`\n(or `tvos` for a tvOS device) before resolving the shipping workspace. `make open`\nprepares both Azahar and Dolphin. Shipping and Tuist share `PVDolphin-Dynamic`;\nSwiftPM owns the bridge, wrapper, Core.plist, Config and Sys resource bundles.\n`make dev-dolphin` prepares its selected slice before generating Tuist.\n\n**PPSSPP:** shipping and Tuist share `PVPPSSPP-Dynamic`. SwiftPM compiles the native\nengine, its dependencies and the Swift/ObjC++ wrapper, and owns Core.plist and PSP\nassets. No CMake/Xcode project preparation is needed. `make dev-ppsspp` opts into\nthe native focused iOS device app; Thin retains the independent libretro core.\nThe checked-in FFmpeg XCFramework includes arm64 device and simulator slices for\niOS and tvOS. Rebuild it with `Scripts/cores/build_ppsspp_ffmpeg.sh`; the legacy\ncodec headers stay private to PPSSPP.\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores, not really used but should be kept updated regardless\n- Each is a multi-platform 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\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n- **CI cache can shadow source changes.** `actions/cache@v4` caches `Cores/`, `CoresRetro/`, `Dependencies/`, `PVRcheevos/` for submodule speed. A post-restore step runs `git checkout HEAD -- Cores CoresRetro Dependencies PVRcheevos` to un-shadow parent-repo-tracked files. If a CI build ignores your source changes, check that this step ran successfully.\n\n- **xcbeautify hides Run Script output — don't debug CI from the console log.** The build pipes through `2>&1 | tee /tmp/xcodebuild.log | xcbeautify`, and xcbeautify filters out Run Script phase output. `GetModule:` / `MakeFrameworks:` lines therefore never appear in `gh run view` or `gh api .../jobs/<id>/logs`, even when the script ran fine — an empty grep means \"filtered\", not \"never ran\". Those lines exist only in the raw log, which is uploaded on failure as artifact `xcodebuild-log-<IPA_NAME>` (3-day retention):\n\n  ```bash\n  gh run download <run-id> -n xcodebuild-log-Provenance-iOS\n  ```\n\n  The failure-report step also prints, in order: `: error:` diagnostics, the `The following build commands failed:` block, and the last 100 raw lines. It greps `\": (fatal )?error:\"` with a **leading colon** on purpose — a bare `\"error:\"` also matches ObjC selectors like `loadStateToFileAtPath:error:` inside `-Wincomplete-implementation` warnings, which floods the report and buries the real errors. Run Script failures (e.g. \"Generate Frameworks\" exiting non-zero) produce no `error:` line at all and surface *only* in the `build commands failed` block.\n\n- **Symbolicating a crash log.** `release.sh` (local and `testflight.yml`) copies each archive's dSYMs to `build/export/<version>-<build>/dSYMs/<ios|tvos>/` and uploads them to Sentry (`provenance-emu/provenance`, needs `SENTRY_AUTH_TOKEN` or `~/.sentryclirc`; a failed upload only warns). Sentry's crash handler is off, so crashes arrive via MetricKit and on-device `.ips` files. To symbolicate by hand, match the `.ips` binary UUID to a dSYM (`dwarfdump --uuid <x>.dSYM`), then `xcrun atos -arch arm64 -o <x>.dSYM/Contents/Resources/DWARF/<name> -l <load address> <address>` (frames in an embedded framework need that framework's dSYM). Prebuilt binaries — libretro buildbot `*.libretro.framework`, ffmpeg, MoltenVK, Sentry, `PVlibDolphin-ios` — ship no dSYM and stay unsymbolicated.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVAppIntents** — Siri and App intents\n- **PVAudio** — Swift audio apis\n- **PVCheevos** — RetroAchievements API integration\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVFeatureFlags** — Feature flags manager\n- **PVHashing** — ROM file hashing for identification\n- **PVHelp** (separate repo, github.com/Provenance-Emu/PVHelp, shared with iCube) — wiki navigation, page fetch/cache and GitBook preprocessing. App-neutral: Provenance's URLs, page paths and offline SUMMARY.md are `ProvenanceWiki` in PVUIBase (`Resources/WikiContent`)\n- **PVJIT** — JIT compilation support for emulator cores\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVLogging** — Logging infrastructure (CocoaLumberjack-based)\n- **PVLookup** (separate repo, github.com/Provenance-Emu/PVLookup, shared with iFly and iCube) — game metadata, artwork and cheat lookup over libretrodb, OpenVGDB, TheGamesDB, ShiraGame and the libretro cheat database. Ships no database bytes (see \"Lookup databases\" below). Change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVLibrarySnapshot** (separate repo, github.com/Provenance-Emu/PVLibrarySnapshot, shared with iCube and iFly) — the App Group snapshot and cover JPEGs the tvOS Top Shelf reads. Provenance's configuration (app group, `provenance://open?md5=` launch URL) is `PVLibrarySnapshotConfig` in PVAppIntents; PVUIBase `LibrarySnapshotWriter` (tvOS only for now) writes it from `WidgetDataWriter.writeFromRealm()`. Widgets and Quick Look still read `PVLibrarySnapshotLegacy` (`widget.*` keys), whose type names collide with the shared package's: no file may import both\n- **PVNetplay** — Central netplay support code for retroarch and game center\n- **PVPLlists** — Core and System plist processing and other serializer\n- **PVPatching** — ROM/disc patches (IPS, IPS32, BPS, UPS, PPF, xdelta3): import, matching, cache, Patch Applier, patch creation, online catalog\n- **PVPatchEngine** — the streaming patch engine PVPatching applies through (bounded memory, 64-bit offsets, vendored Apache-2.0 xdelta3). Its own repo, github.com/Provenance-Emu/PVPatchEngine, which iCube and iFly also depend on by URL: change it there, tag a release, bump the version in `PVPatching/Package.swift`. Only `PVPatching/Sources/PVPatching/Engine/EngineBridge.swift` may import it (its `PatchFormat`/`PatchError`/`PatchProgress` clash with PVPatching's)\n- **PVPrimitives** — Base data types shared across modules\n- **PVSystems** (separate repo, github.com/Provenance-Emu/PVSystems) — `SystemIdentifier` and per-system metadata (TheGamesDB/libretrodb/OpenVGDB IDs, RetroAchievements console IDs, per-game BIOS manifest). Shared with iCube and iFly: change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVQuicklookSupport** — iOS and macOS Quicklook api support code\n- **PVRcheevos** — RetroAchievements C client integration\n- **PVSettings** — User preferences\n- **PVShaders** — video filters: Provenance's hand-written Metal filters, the bundled libretro slang library (runtime in `PVSlangRuntime`), filter catalog/selection store, previews, non-linear stretch\n- **PVSupport** — Shared utilities\n- **PVThemes** — UI Theming Support\n- **PVTouchOverlay** — Programmatic on-screen controller: layout families, live editor, shared touch primitives\n- **PVUI** — SwiftUI-based shared UI components\n- **PVWebServer** — Swift/Objective-C SwiftPM module for GCDWebServer and WIP new Swift webserver for webdav and http file management, future REST API\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n**Core taxonomy — not every `Cores/PV*` directory is an active shipping core.**\n- **Active native PV* cores** (custom forks or long-supported legacy we\n  actively extend): Azahar, Dolphin, FCEU, Genesis-Plus-GX, Mednafen,\n  Mupen64Plus, ProSystem, snes9x, Stella, plus mGBA, PicoDrive and TGBDual\n  for native-only features. Source of truth:\n  `docs/superpowers/specs/2026-10-10-core-audit.md` (KEEP rows). Retired\n  cores live on as `PVCore.retiredCores` entries (`RetiredCoreMigration`,\n  `RetiredBatterySaveMigration`); `Cores/Debug` stays for its `PVDebug.c` simulator stub; no new `Cores/` project without an audit row.\n- **Deprecated native cores** (`PVCore.deprecatedCores`): Atari800, Bliss,\n  CrabEMU, Gambatte and VisualBoyAdvance-M still ship because they shipped in\n  3.3.x and users have save states on them that the libretro dylibs cannot\n  load, but the libretro cores are the recommended choice. Pickers label them \"(Deprecated)\" and sort them last;\n  nothing migrates away from them. Don't extend them.\n- **Pruned (2026-10, `feature/prune-dead-cores`):** 27 `Cores/` projects that\n  duplicated a libretro buildbot dylib or were never wired were deleted:\n  PokeMini, VirtualJaguar, O2EM, Desmume2015, melonDS, BeetlePSX, DosBox, DuckStation,\n  FreeIntv, GameMusicEmu, Gearcoleco, JollyGoodEmulation, Mini_vMac, Mu,\n  Mupen64Plus-NX, Play, Potator, Reicast, Sudachi, VecX, Yabause, fuse, opera,\n  pcsx_rearmed, sm64ex, snesticle, supergrafx. Their games run on the thin\n  wrapper through the replacement table in `PVCore.swift` where a replacement exists (17\n  entries); cores that were disabled or never shipped have no entry.\n- **Rule of thumb:** if a libretro buildbot dylib already serves the\n  core and the thin wrapper handles it, fix the thin wrapper (or the\n  upstream dylib via Provenance fork), not a native PV* shell.\n  The whole point of the thin wrapper is to make those cores feel\n  basically native without per-core PV* maintenance.\n\n**Wrapper platform defaults — patch the right file:**\n- **Both iOS and tvOS** now default to the THIN libretro wrapper\n  (`PVCoreBridgeRetro/Sources/PVLibRetro/PVThinLibretroFrontend.mm`\n  + `PVThinLibretroCore*.swift`). dlopen + function pointers against\n  the libretro buildbot dylibs; no full RetroArch runtime.\n- **PPSSPP runs on the THIN wrapper, on Vulkan** (no force-route since `2f09fd7802`, 2026-05-28). On GLES, PPSSPP spawns its own render thread (`useEmuThread = GPUCORE_GLES`) that never has a current EAGL context, so GL can't work on thin; `GET_PREFERRED_HW_RENDER` returns Vulkan for PSP and GL is rejected (`e66b2a28a4`, `79a091c4b2`), with `context_reset` deferred until after `retro_load_game`. A failed deferred Vulkan setup now fails the load, and fence waits are bounded (`f87b9d17e1`).\n- **Thin `rendersToOpenGL` is true for every HW-render core, Vulkan included.** `PVThinLibretroFrontend` returns `_hwRenderRequested`, so a Vulkan core takes the \"OpenGL\" branch of `PVMetalViewController.draw(in:)` and never reaches the software-upload path. `rendersToVulkan` is the one that distinguishes them.\n- **Thin core options are replayed at boot, not read on demand.** The frontend holds option values only in memory; `PVThinLibretroCore.applyPersistedCoreOptions()` pushes the saved ones in from `afterCoreInitBlock` (after `retro_init`, before `retro_load_game`). The UI stores a switch state or a choice *label*; `ThinCoreOptionDefinition.rawValue(forStored:)` maps it to the core's value. A replay that never finishes booting is skipped once on the next launch.\n- The thick RetroArch wrapper (full in-process RetroArch, `PVRetroArch.framework`) has been removed; every libretro core runs on thin. What remains under `CoresRetro/RetroArch/` serves thin: `Core.plist` (the libretro core list, bundled via the `PVCoreLoader` resource symlink `RetroArchCore.plist`), `scripts/` (dylib download/packaging, `cores.yml`), `modules/` and `libretro-super`. A core from that list is available (`PVCore.hasCoreClass`) only when its `<name>.libretro.framework` is in the app bundle. Its top-level `com.provenance.core.retroarch` entry (the old launcher) is `PVDisabled`; its `PVRetroArch.PVRetroArchCoreCore` principleClass strings stay because `PVCoreFactory` keys on them.\n- **Thin netplay is libretro netpacket (env 78) only** — cores that register it (gpSP, melonDS DS) get netplay; RetroArch rollback netplay went with the RetroArch runtime. `NetpacketTransport` (PVNetplay) carries packets over framed TCP; `PVThinLibretroFrontend` hands every netpacket callback to the core on the emulation thread from a per-frame pump before `retro_run`, never from the network queue. Hosts advertise Bonjour `_provenance-np._tcp`, which must stay listed in every app Info.plist's `NSBonjourServices`. Cores with their own netplay socket (Dolphin ENet, mGBA, Mednafen, PPSSPP ad hoc) set `netplayAdvertisesOwnRoom = false` (the default) and `PVNetplayManager.host(settings:game:)` publishes the room for them (`NetServiceRoomAdvertiser`); TXT keys live in `NetplayRoomTXTRecord`. Without that, a LAN room is invisible to Browse.\n- **Thin wrapper gaps:** deferred Vulkan context setup (GL is now eagerly activated; Vulkan still deferred). BIOS files synced from `BIOSPath` into system directory before `retro_load_game`. `ScalingMode` integration works (via `PVMetalViewController` + DeltaSkin skin container). Fast-forward wired (`setGameSpeed:` override syncs `_speedMultiplier` + `_audioPaused`).\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access (see DEVELOPER.md for patterns)\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying third-party upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n### Build & toolchain gotchas\n\n- **Most workspace `.xcodeproj`s list sources explicitly** (e.g. the remaining native core projects), so a new file compiles in SwiftPM yet breaks the archive build. `Scripts/audits/check_pbxproj_sources.py` (CI: `xcode-project-sources.yml`) flags a source file sitting beside compiled ones that its project doesn't reference.\n- **Every script under `Scripts/` or `CoresRetro/RetroArch/scripts/` must be in `Scripts/maint/jobs.toml`** (as a job, or under `[ignore]`); `maint.yml` fails PRs that add an unregistered one. `make maint` (dashboard) / `python3 Scripts/maint/maint.py status` show which generated files are stale. Scripts that find the repo root from their own path sit two levels deep (`Scripts/<category>/`).\n- **PVUI cannot build inside a git worktree.** The PackageBuildInfo plugin reads `.git/HEAD`; a worktree's `.git` is a file. Run PVUI tests from an rsync'd copy with a fake `.git/HEAD` and submodule symlinks (see the Phase 1 overlay plan, Global Constraints).\n- **`gh issue list` has no `--sort` flag.** Use `gh issue list --search \"sort:created-desc\"` or `gh issue list --json number,title,createdAt --jq '.'` for sorted/filtered queries.\n\n### Dev workspace (Tuist)\n\nA second, generated workspace for fast iteration. `Provenance.xcodeproj` still ships\n(CI, fastlane, release.sh). Spec: `docs/superpowers/specs/2026-10-10-dev-workspace-design.md`.\n\n- **Generate / open:** Tuist 4.200.0 is pinned in `.mise.toml`. Run `mise exec -- tuist generate --no-open`\n  (or `make dev-generate`) from the repo root; `make dev` also opens `Provenance-Dev.xcworkspace`.\n  Generated files (`Provenance-Dev.xcworkspace`, `Dev/Provenance.xcodeproj`, `Dev/Derived`) are\n  gitignored. Never commit them. The Tuist project is named `Provenance` and lives in `Dev/`\n  because `Build.xcconfig` derives bundle ids, the app group and the iCloud container from\n  `$(PROJECT_NAME:lower)`.\n- **Focused apps:**\n  - `Provenance-Dev-UI`: mGBA, Stella, snes9x.\n  - `Provenance-Dev-Thin`: no native cores; 4 libretro dylibs (mednafen_psx_hw, mupen64plus_next, snes9x, ppsspp).\n  - `Provenance-Dev-Azahar`: Azahar.\n- `Provenance-Dev-EmuThree` (opt-in, iOS device): `make dev-emuthree` uses the same\n  `PVEmuThree-Dynamic` SwiftPM product as shipping. The package compiles the legacy engine\n  and bridge sources and selects the arm64 iOS/tvOS device or simulator vendor slice;\n  no core Xcode project is needed. Rebuild those slices with\n  `python3 Scripts/cores/build_emuthree_vendors.py`.\n  - `Provenance-Dev-Dolphin` (opt-in, device only): `Cores/Dolphin/Package.swift` wraps\n    `PVlibDolphin.xcframework` as a `.binaryTarget`. SwiftPM will not load the package until\n    `build_slice.py dolphin <slice>` has produced the xcframework, so `make dev-dolphin` runs it first\n    and sets `TUIST_DEV_DOLPHIN=1`; without that variable the app and package are not in the generated\n    project (CI never sets it). The ObjC++ target reaches the Swift module through the runtime\n    (`#if SWIFT_PACKAGE` in `PVDolphinCore.mm`), because SwiftPM can't build the mixed target as one.\n\n  Build with `make dev-ui|dev-azahar|dev-thin` (derived data in `build/dev-dd`; override with\n  `DEV_DERIVED=`). Each app is one iOS+tvOS target with bundle id\n  `org.provenance-emu.provenance.dev.<slug>`.\n- **Simulator builds are ad-hoc signed by `Dev/Config/Dev.xcconfig`.** Never pass\n  `CODE_SIGNING_ALLOWED=NO` to a dev build: the app then dies in dyld on the unsigned embedded\n  MoltenVK.\n- **Dev apps are arm64-only; Intel is unsupported** (decided 2026-10-12: no Intel Mac runs a\n  supported Xcode). `DevSettings` sets `EXCLUDED_ARCHS[sdk=*simulator*] = x86_64` for every focused\n  app, and the Makefile, harness and both CI workflows also pass `EXCLUDED_ARCHS=x86_64` to\n  xcodebuild, because a project setting never reaches package targets (CI kept compiling x86_64\n  for every package without it). Prebuilt cores need no x86_64 slice and SwiftPM core targets need\n  no per-arch source lists. Don't add x86_64 workarounds (stubs, generic-source fallbacks) here.\n- **The dev workspace has no committed Package.resolved, so it resolves the newest remote\n  packages.** Anything that only works because `Provenance.xcworkspace`'s resolved file pins an\n  older version breaks here first. Known case: Hummingbird imports `ServiceContextModule` without\n  declaring it, and swift-service-context 1.4.0 / swift-distributed-tracing 1.6.0 (2026-10-12)\n  dropped it from `Tracing`'s dependency chain, so builds failed at random with \"no such module\n  'ServiceContextModule'\"; `PVWebServer/Package.swift` caps both packages. Fix such breaks with a\n  version cap in the declaring package, not with xcodebuild flags.\n- **Helpers:** `Tuist/ProjectDescriptionHelpers/` holds `CoreProduct` (the core table),\n  `FocusedApp` (the target template plus `FocusedApp.all`), `LibretroCores` and `DevSettings`. Tuist\n  only finds helpers at `<root>/Tuist/ProjectDescriptionHelpers`; don't add a `Dev/Tuist/`.\n- **How cores get in:**\n  - SwiftPM dynamic cores are embedded with `.package(product:, type: .runtimeEmbedded)`.\n  - Cores from a hand-maintained `Cores/<X>/*.xcodeproj` are linked by `-framework <Product>` in `OTHER_LDFLAGS`.\n    That project is referenced from `Workspace.swift` (Xcode then builds it as an implicit dependency),\n    and the \"Embed core frameworks\" post script copies and signs the framework. Tuist can't\n    depend on targets of projects it doesn't generate.\n- **Libretro dylibs per app:**\n  - `FocusedApp.libretro` names (cores.yml) become a pre script calling\n    `CoresRetro/RetroArch/scripts/get-modules.sh --urls <file>`, plus a post script running\n    `make_frameworks_retroarch.sh` and `validate_frameworks.sh`.\n  - `--urls` keeps its own state dir (`modules_compressed/<iOS|tvOS>-urls-<sha12>`) and never prunes the shared `modules/`.\n  - The buildbot dylibs are iOS-platform binaries and cannot `dlopen` in a simulator, so\n    **Provenance-Dev-Thin plays games on a device only**.\n- **Manifest checks:** `Scripts/dev/check_dev_manifest.sh` compiles the helpers against Tuist's\n  `ProjectDescription.framework` and checks three things: every libretro name is enabled in cores.yml,\n  every path exists, and no local package is declared twice. Prebuilt artefacts (e.g. `PVlibDolphin.xcframework`) are\n  required only for rows a `FocusedApp` uses.\n- **Harness:** `make dev-harness ROM=<path> TARGET=ui|azahar FRAMES=n CORE=<id> SIM_DEVICE=<udid|booted>`\n  builds the app, launches it with the `-PVHarnessROM`, `-PVHarnessCore`, `-PVHarnessFrames`,\n  `-PVHarnessOut` and `-PVHarnessExit` launch arguments, and copies `screenshot.png`, `frames.json`, `log.txt` (or\n  `error.txt`) to `build/harness/<Scheme>/`. `TARGET=thin` is refused (exit 2): Thin runs on a device only.\n  Synthetic test ROMs come from `Scripts/dev/make_harness_rom.py` (2600 and gba). Frame counts are\n  time-based estimates (`\"frameCountSource\": \"estimated\"`). Its first catch was a native Stella VFS\n  regression (`16da1e4c40`).\n- **Core slices:** `Scripts/cores/build_slice.py <azahar|dolphin> <ios|ios-sim|tvos|tvos-sim>\n  [--print-key|--force|--xcframework|--cache-dir DIR]` builds or links one slice.\n  - The key is a content hash: submodule working tree (dirty edits bust the cache; `--force` rebuilds) and\n    nested externals, build script and toolchain, flags, Xcode and SDK, and Azahar's MoltenVK slice.\n  - Dolphin keys only on the dolphin-ios paths its CMake build reads (`DOLPHIN_BUILD_PATHS`: top-level\n    CMake files, `Externals`, `Source/Core`, `Source/iOS/Library`, `ApprovedInis.json`), as HEAD tree\n    hashes plus dirty edits under them. iCube app commits and edits there no longer force a rebuild;\n    if the build starts reading a new path, add it to that list.\n  - The cache is `$PV_CORE_CACHE` or `~/Library/Caches/Provenance/cores/<core>/<slice>/<key12>/`.\n  - On a hit, the legacy `build/xcframework/<Product>-<slice>.framework` path is a symlink into it.\n  - Only `--xcframework` repacks the multi-slice xcframework (Dolphin's is repacked after every miss).\n    If its hard links fail, set `PV_CORE_CACHE` to a folder on the repo's volume.\n- **CI:** `build.yml` and `testflight.yml` cache `~/Library/Caches/Provenance/cores` per platform,\n  keyed on `build_slice.py --print-key`. `dev-workspace.yml` smoke-builds `Provenance-Dev-UI` on\n  both simulators (Tuist via mise, which needs `GITHUB_TOKEN`).\n- **Pruning rule:** no new `Cores/` project without an audit row in\n  `docs/superpowers/specs/2026-10-10-core-audit.md`.\n\n### Azahar core build gotchas\n\n- **Shipping Xcode and Tuist consume `PVAzahar-Dynamic` from `Cores/Azahar/Package.swift`.** Prepare its cached CMake-built `PVlibAzahar.xcframework` before package resolution: `make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim`). `make open`, dev generation, CI and release entry points do this automatically. A cold slice takes ~30–40 min and needs cmake, ninja and python3 ≥ 3.10. The content key includes the submodule/externals, builder, toolchain, Xcode/SDK and MoltenVK slice; a hit reuses native output and refreshes `build/headers`. Force a rebuild with `build_slice.py azahar <slice> --force --xcframework`.\n- **The package graph always declares the native binary.** Missing output must be prepared before resolving; a build phase cannot produce an XCFramework early enough. The package's ObjC++ bridge (`PVAzaharBridge`) must never import the Swift module (`PVAzahar-Swift.h`): that is a target cycle. The Swift core pushes `renderViewClass` and `userDirectoryPath` into it from `init`, and runs its per-load setup (options, `respectsMuteSwitch`, emuThreeDS import) from the bridge's `willLoadFile` hook. A `loadFile` override on a Swift core never runs: `PVEmulatorViewController.createEmulator` calls `bridge.loadFile` for every `ObjCBridgedCore` (and `PVEmulatorCore.loadFile` only throws), which is how 3.4 TestFlight shipped \"Azahar has no user directory\".\n- **PVlibAzahar is arm64-only, and so is every simulator build.** `PVAzahar` excludes x86_64 simulator slices, as do `Build.xcconfig` (the shipping app) and the dev apps; the old link-only x86_64 stub is gone. Package targets ignore project settings, so generic-simulator builds also pass `EXCLUDED_ARCHS=x86_64` to xcodebuild.\n- **Never include azahar's `core/hle/service/nwm/nwm_uds.h`.** Azahar headers come in with `-idirafter`, so its `#include \"network/network.h\"` resolves to the SDK's Network framework header instead. The bridge includes azahar's own copy as `\"network.h\"` via a narrow `HEADER_SEARCH_PATHS` entry.\n- **Emulator changes go on the fork.** Commit them to `Provenance-Emu/azahar` branch `provenance` and list them in `Cores/Azahar/PATCHES.md`. Never edit `Cores/Azahar/azahar` in place.\n- **The tvOS user root is Caches.** `PVAzaharCore.userRootURL` is `Documents/Azahar` on iOS and `Library/Caches/Azahar` on tvOS, where Documents is not writable.\n- **`-fcxx-modules` is on** so the ObjC++ glue can `@import` its PV* dependencies. It must not import `PVAzahar-Swift.h`.\n- **`runOnEmuThreadAndWait:` jobs can outlive their timeout.** Capture only by value or `shared_ptr`, never by reference.\n- **PVAzahar links with `-unexported_symbols_list unexported_symbols.txt` (`__Z*`).** dyld coalesces *exported* weak C++ definitions (template instantiations, inline functions) across every loaded image. PVlibDolphin loads first and exports its own glslang and fmt instantiations from different library versions, and azahar's shader compiler crashed in glslang's parser after calling Dolphin's `addSwizzle`. Keep the C++ surface private; apply the same to any new core framework that static-links glslang, fmt, SPIRV-Cross or similar. PVEmuThree uses the same list. PVlibDolphin cannot (the PVDolphin bridge imports Dolphin's C++ API, including weak `MMU::HostRead<>` instantiations), so dolphin-ios's `BuildiOSXCFramework.py` relinks a second time with a generated list of third-party weak symbols via `-DDOLPHIN_UNEXPORTED_SYMBOLS_LIST`; the global `CMAKE_SHARED_LINKER_FLAGS` never reaches the `dolphin` target. Audit a built app with `nm -m <fw> | grep 'weak external'` per framework and `comm -12` the sorted lists.\n- **Keep C++ visibility filtering at the final framework link.** Compile-time hidden visibility lets Xcode's SwiftPM `ld -r` step localize bridge definitions before the Swift module and engine can resolve them. `Package.swift` forwards `-ObjC` with `-Xlinker` for the Swift link driver; `unexported_symbols.txt` hides C++ symbols in the final image.\n\n### Metal rendering gotchas\n\n- **`CAMetalLayer.nextDrawable` blocks forever when backgrounded.** Metal reclaims drawables during background. The CA runloop observer fires `MTKView.draw(in:)` before `didBecomeActiveNotification`, so `currentDrawable` deadlocks the main thread. ALL `draw(in:)` implementations MUST early-return when `UIApplication.shared.applicationState != .active`.\n- **`@synchronized(self)` in emulation loop vs main thread.** The emu loop holds `@synchronized(self)` around `executeFrame` at 60fps. Any main-thread code that acquires the same lock (e.g., `setPauseEmulation`) will starve. Set atomic flags BEFORE acquiring the lock so the emu loop sees the change and releases.\n- **Thin wrapper reports its geometry/aspect/sample-rate LATE.** `PVThinLibretroFrontend` populates `_rawAVInfo` (geometry, aspect, `timing.sample_rate`, fps) only at the END of `startWithROMPath` — which runs inside `startEmulation`, NOT during `loadFile` (that only dlopens the core). But `createEmulator()` sets up the audio graph (`setupAudioGraph`/`startAudio`) AND the DeltaSkin viewport BEFORE `core.startEmulation()`. So anything that reads the core's geometry/aspect/sample-rate at view/audio-setup time gets the fallback defaults (`?: 640/480/256`, `audioSampleRate → 44100`), not the real values. Native/thick cores populate av_info during `loadFile`, so they don't hit this. This has caused: (1) thin-wrapper audio built at 44100 then never rebuilt → bassy/glitchy (fixed by rebuilding the audio graph after start if the rate changed, #cb40fef061); (2) skin render view stuck full-screen/wrong-size until rotation because the viewport was computed before geometry settled (fixed by recomputing on layout-settle, #a63e1622f1). Pattern for any new feature that reads core geometry/rate: recompute/refresh AFTER `startEmulation`, gated on the value actually having changed.\n\n### Shader / filter system\n\n- **The slang library is fetched at build time, never committed.** `Scripts/shaders/fetch_slang_shaders.py` downloads the release pinned in `Scripts/shaders/slang-shaders.lock.json` (JoeMatt/slang-shaders-metal, `.oecompiledshader` zips compiled from libretro slang by OpenEmu-Shaders' `oeshaders`), unpacks each preset into a `shader.json` directory (no runtime unzip), merges the hand-written presets in `PVShaders/Library/native-metal/`, and writes `manifest.json`. Every app target (shipping xcodeproj and Tuist `FocusedApp`, `slangShaders:`) copies it into the bundle as `SlangShaders/` via `embed_slang_shaders.sh`. To bump the snapshot: change the lock, run `Scripts/shaders/validate_slang_shaders.sh` (renders every preset on the Mac GPU, writes `slang-shaders-excluded.json`), commit both.\n- **Which filter draws is per game.** `ShaderLibraryStore` (PVShaders) holds selections for global / `system:<id>` / `game:<md5>` scopes (game wins), parameter overrides, favorites and recents; selections, parameters and favorites sync through iCloud KVS as timestamped per-key maps (`StampedMap`, merged both ways via `iCloudSettingsSync.register`). Native filters keep their values in their old `Defaults` keys. The legacy `metalFilterMode` key is still bridged into the global selection; its raw values are case names (`Always(vhs)`, not `Always(VHS)`).\n- **`ShaderSelection.off`, not `.none`.** A `.none` case on an enum passed as `ShaderSelection?` silently becomes `Optional.none` and clears the scope.\n- **Renderers:** `PVMetalViewController` resolves the filter every frame (native → `PVMetalFilterRenderer`, library → `SlangFilterRenderer`, which compiles presets off the main thread and draws the plain picture until ready). Cores that present from their own thread (Dolphin Metal backend, native PPSSPP) use `ShaderPostProcessor`, fed a resolved snapshot on `ShaderLibraryStore.didChangeNotification`. Dual-screen layouts and Azahar are never filtered (pause tiles hidden via `filtersApplyToCurrentLayout`); `LCDScreenFilter` (CIFilter) is the intended DS/3DS look.\n- **Filters draw into the picture rect, not the drawable.** Until a skin reports its screen rect the GPU view is full screen; `RenderGeometry.contentRect` letterboxes the picture inside it. Code that draws over the frame must use the same rect (this was the \"VHS covers the whole screen\" bug).\n- **`swift test` in PVShaders runs real Metal tests on the host GPU**, but the SwiftPM CLI only copies `.metal` files: the slang runtime compiles its bundled source as a fallback, while native-filter pipelines (package metallib) exist only in Xcode builds, so those tests skip.\n\n### Lookup databases (PVLookup)\n\n- **The databases are fetched at build time, never committed.** `Scripts/lookup/databases.lock.json` pins five assets by sha256 on a `databases-YYYY-MM-DD` release of Provenance-Emu/PVLookup. Every app target (shipping xcodeproj \"Script: Embed Lookup Databases\", Tuist `DevSettings.embedLookupDatabasesScript`) runs `embed_lookup_databases.sh`, which fetches them into the gitignored `LookupDatabases/` (cache: `~/Library/Caches/PVLookupDatabases`) and copies the zips to the bundle ROOT, where PVLookup's default `LookupDatabases` source looks for `<file name>.zip` in `Bundle.main`. No `LookupDatabases.configure` call is needed. `make lookup-databases` fetches by hand.\n- **`fetch-lookup-databases.sh` is a copy of PVLookup's canonical script**; keep it identical. To bump the databases: publish a new release in the PVLookup repo, then update the lock file.\n- **The cheat database generator still lives here.** `make update-cheatdb` writes `build/cheatdb/libretro_cheats.sqlite.zip` for publishing as a PVLookup release asset; the app never builds from it.\n- **PVLookup logs through `PVLookupLog`**, routed into PVLogging by `LoggingBootstrapTask` on a serial queue: PVLogging's `log()` holds four `Logger` interpolations, and calling it at the bottom of a deep lookup overflowed a 512 KB concurrency stack in a Debug build (iFly).\n- **`PVLookupTypes` and `TheGamesDB` are not products.** Their imports in PVLibrary/PVUI resolve through the `PVLookup` product's transitive modules, as they always did.\n\n### Debugging emulator cores\n\n- **\"Black screen\" reports: ask for the `[NO-FRAME]` log line.** Five seconds after a successful boot with nothing presented, `PVEmulatorViewController+FirstFrameWatchdog` logs the presenter, render path, pause state, geometry and the GPU view's frame/visibility. Cores that draw to their own view (native Dolphin, PPSSPP, RetroArch `skipLayout`) trip it by design.\n- **Per-game core options depend on `CoreOptionsContext.currentGameMD5`.** It is set in `initCore()` before the core initializes and cleared in `quit`; `CoreOptional.currentGameMD5` defaults to it. Without it a core reads only the core-wide key and per-game overrides are silently ignored.\n- **flycast cannot be debugged with Xcode attached.** It installs `signal(SIGSEGV, ...)` for VRAM lazy-mapping; Xcode catches SIGSEGV and pauses, breaking the core. Use Console.app (filter `Process = Provenance`) for live logs OR `iOS Settings → Privacy & Security → Analytics → Analytics Data` for `.ips` crash files post-mortem.\n- **Sentry's `enableCrashHandler` is disabled** at `SentryBootstrapTask.swift:48` because its SIGSEGV handler conflicts with flycast's MMU path. Do NOT re-enable it. Crash telemetry flows through MetricKit instead.\n- **iPad MoltenVK surface_caps lie.** On iPadOS 26 with Stage Manager / adaptive scaling, `VkSurfaceCapabilitiesKHR.currentExtent` / `minImageExtent` / `maxImageExtent` ALL report `view.bounds × contentScaleFactor`, NOT the actual `CAMetalLayer.drawableSize`. They can differ (e.g. 2732×2048 vs 2092×1568). The authoritative source for iOS Vulkan is `metalLayer.drawableSize`. Never clamp against MoltenVK surface caps on iOS.\n- **libretro core option key prefixes don't always match the core name.** flycast's options are `reicast_*` (per `CORE_OPTION_NAME` in `shell/libretro/libretro_core_option_defines.h`). Always grep the core's `libretro_core_options.h` before guessing key names.\n- **Jaguar numpad uses keyboard input.** The upstream `virtualjaguar-libretro` core reads numpad buttons 0-9/*/# via `RETRO_DEVICE_KEYBOARD` (`RETROK_0`-`RETROK_9`, `RETROK_MINUS`, `RETROK_EQUALS`), NOT joypad buttons. Buttons 7-9/*/# have NO joypad mapping. The thin wrapper dispatches both keyboard events (via `_bridge.setKeyState`) and joypad presses for compatibility.\n\n### Code conventions\n\n- **Library storage: `LibraryLocation` owns the roots; never move library folders by hand.** `URL.documentsPath`/`cachesPath` and `LibraryLocation.current` share one launch-fixed precedence (stored `libraryLocation` → legacy `useAppGroups`, both snapshotted at launch). Flipping `useAppGroups` alone no longer moves files.\n  - Moves go through `LibraryMoveCoordinator` (PVFileSystem `LibraryMove/`): journaled in the app's Application Support, verified (size; MD5 under `ROMs/`+`BIOS/`), switched by writing the setting, and cleaned up on the *next* launch after a delta sweep.\n  - Leaving the App Group moves every top-level entry except hidden files, `.realm` names and the journal, including entries created after the move started. \"Identical\" means size **and** MD5, in every folder. A copy-phase conflict moves the destination's own file aside to `Conflicts/` and puts the live file at the real path; the post-switch sweep files a changed source under `Conflicts/`. Nothing is ever deleted or overwritten, and copies go through a hidden temp file and a rename.\n  - \"RESET TO DEFAULT\" must not touch `libraryLocation`/`useAppGroups`. Lite and the full app share the group, so a move keeps a copy while the other app still uses it.\n  - A folder picked in Files is `LibraryLocation.pickedFolder`: the security-scoped bookmark is `Defaults[.libraryPickedFolder]`, resolved once per launch by `LibraryLocation.pickedFolderAtLaunch` (never stopped; a failure is `.unavailable` and the UI offers re-pick / App storage / retry). It means two roots: `rootURL` for `ROMs/`, `BIOS/`, `System/` and `savesRootURL` for the five saves folders, which join the picked folder only when `includesSaves` is on. `LibraryFolderInspector` gates every pick (writable, not the library itself, not inside a moved folder). The journal is version 2 (`sourceFolder`, `destinationFolder`, `deduplicateByMD5`, `remaps`).\n  - Joining a folder that already has `.provenance-library.json` de-duplicates `ROMs/` and `BIOS/` by size + MD5 under any name; the resulting `remaps` must be applied to the database before cleanup deletes sources, so a remap applier (`LibraryPathRemapper`) has to be registered before `resumeIfNeeded()` (`PVGameLibrary.init` does this). iCloud-located picked folders are watched by `PickedFolderCloudWatcher`.\n- **Changing a game's MD5 goes through `GameRekey`, never a delete.** `PVGame.md5Hash` is the Realm primary key, so a ROM rewritten in place (\"Replace the Original\") needs a new row: `GameRekey.rekey(gameWithMD5:to:crc:fileSize:in:)` copies the game (keeping `id`), re-points saves, cheats, recents, patches, libraries and controller profiles, then raw-deletes the old row. `RomDatabase.delete(game:)` would delete the ROM file, saves and patches. The library MD5 is the importer's (`GameImporterDatabaseService.calculateMD5(forGame:)`, header-aware), not the raw file MD5. Replacing is blocked while iCloud Sync is on: another device's soft-delete of the old game removes the file at the shared path.\n- **Notification names — no magic strings.** Declare `FOUNDATION_EXPORT NSNotificationName const FooBarNotification;` in an ObjC `.h`, define in the matching `.mm`, mirror via `Notification.Name` extension in Swift referencing the same string. Posters reference the const; observers reference `Notification.Name.fooBar`. Single string literal lives in one `.mm` — never at a call site.\n- **Email in public-facing files / comments:** use `git@joemattiello.com` (SECURITY.md, README, GitHub issue comments, anything that ships publicly).\n- **ObjC categories in SwiftPM modules can be silently elided.** When a category is declared in a separate `.h` file referenced by a modulemap, Swift module synthesis may drop it entirely — no error, just \"no member\" at call sites. Workaround: declare methods in the main `@interface` block. Known to affect `(LightGun)` categories on PVStellaBridge and PVProSystemGameCore. Other categories (Cheats, Trackball) in the same files work fine — root cause unclear.\n\n### Subagent worktree rules\n\n- Subagents in `isolation: worktree` MUST stay on their own branch — never `git reset` / `rebase` / `push` / touch `develop`. Their commits get cherry-picked onto develop afterward.\n- Always include \"DO NOT git reset / rebase / push / touch develop\" in subagent prompts. Without it, parallel agents have historically collided (one agent ran `git reset HEAD~1` and dropped another agent's commit).\n- After an agent reports completion: cherry-pick from the agent's branch onto develop in the MAIN worktree's `Provenance/` directory. Shell can land in the agent's worktree if you're not careful — verify `pwd` and `git branch --show-current` before each cherry-pick.\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# PVUI unit tests (PVUIBaseTests + PVSwiftUITests). Use the committed\n# PVUI-UnitTests scheme, not the auto-generated PVUI one: that also builds the\n# snapshot suite, whose Prefire-generated code doesn't compile. On Xcode 26.6\n# realm-core needs `-xcconfig` with OTHER_CFLAGS/OTHER_CPLUSPLUSFLAGS =\n# $(inherited) -Wno-invalid-specialization (CI's Xcode 26.3 doesn't).\ncd PVUI && xcodebuild test -scheme PVUI-UnitTests \\\n  -destination 'platform=iOS Simulator,name=iPhone 17' \\\n  CODE_SIGNING_ALLOWED=NO -skipPackagePluginValidation -skipMacroValidation\n\n# PVLibrary tests: the committed PVLibrary-UnitTests scheme, through the\n# package workspace (PVLibrary/PVLibrary.xcodeproj shadows Package.swift, and\n# the auto-generated scheme has no test action). Same realm-core -xcconfig on\n# Xcode 26.6. CI: pvlibrary-tests.yml (develop pushes + PRs via agent-validation).\ncd PVLibrary && xcodebuild test -workspace .swiftpm/xcode/package.xcworkspace \\\n  -scheme PVLibrary-UnitTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  -collect-test-diagnostics never CODE_SIGNING_ALLOWED=NO \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# PVCoreBridgeRetro (thin wrapper) tests: PVLibRetroTests in Package.swift.\n# Open the package in Xcode and select its test scheme, or use its package workspace.\n# Ad-hoc signing is required for MoltenVK.\nxcodebuild test -workspace PVCoreBridgeRetro/.swiftpm/xcode/package.xcworkspace -scheme PVCoreBridgeRetro \\\n  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  CODE_SIGN_IDENTITY=- CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM= \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Agents should scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode (PVShaders: `swift test` works) |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **GitHub workflow files** — `.github/workflows/*.yml` may be edited when fixing CI issues or optimizing builds. However, GitHub Actions bot PRs cannot push workflow changes (requires `workflows` permission) — if an agent PR needs to include workflow changes, note this in the PR description so a maintainer can push them manually.\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are third-party upstream code (do not casually fork in place)\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **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. Use `PBXFileSystemSynchronizedRootGroup` for source directories (Xcode 16+). Prefer minimal diffs — only touch the sections that need changing.\n\n### Minimum Deployment Targets\n\nProvenance ships for **iOS 17+ and tvOS 17+**. The PV* packages also declare **macOS 14+ and visionOS 1+** so keep code compiling for them with platform guards, but no Catalyst/native-mac/visionOS app target ships today (native macOS is planned — see the 2026-08-07 macOS strategy spec). 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.\n\n**Prefer modern Swift/SwiftUI APIs** when the minimum deployment target supports them:\n\n| Prefer (iOS 16+/17+) | Over (older) |\n|---|---|\n| `ShareLink` | `UIActivityViewController` wrapped in `UIViewControllerRepresentable` |\n| `@Observable` macro (iOS 17+) | `@ObservableObject` + `@Published` |\n| `NavigationStack` | `NavigationView` |\n| `.navigationBarTitleDisplayMode(.inline)` on non-tvOS | conditional guard |\n| `UIWindowScene.keyWindow` | `UIApplication.shared.keyWindow` (deprecated iOS 13) |\n\n**`@StateObject` vs `@ObservedObject` rule:**\n- `@StateObject` — use only when the view **creates and owns** the object's lifetime (new instances).\n- `@ObservedObject` — use for **singletons** (e.g. `Foo.shared`) and objects passed in from outside.\n  Using `@StateObject` with a singleton is semantically wrong even though it compiles.\n\n### Pre-PR Validation (MANDATORY)\nAgents MUST run these checks before creating a PR. Do NOT skip any step.\n\n1. **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).\n\n2. **Lint** — Run `swiftlint lint --path <file>` on every changed Swift file.\n\n3. **Unit tests** — If the module has tests, run them: `cd PV<Module> && swift test`. If adding new logic, add test coverage.\n\n4. **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.\n\n5. **No magic numbers** — Extract constants. Don't hardcode values that are used in multiple places.\n\n6. **Use `SystemIdenti","isInternal":false,"tokens":13003,"sizeBytes":50163},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# Copilot Instructions for Provenance\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.2 (`/Applications/Xcode_16.2.app`)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores\n- Each has iOS and tvOS variants\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVUI** — SwiftUI-based shared UI components\n- **PVSettings** — User preferences\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVSupport** — Shared utilities\n- **PVLogging** — Logging infrastructure (OSLog-based; use `DLOG`/`ILOG`/`ELOG`/`WLOG`)\n- **PVPrimitives** — Base data types shared across modules\n- **PVCheevos** — RetroAchievements integration\n- **PVHashing** — ROM file hashing for identification\n- **PVLookup** — Game metadata lookup (separate repo, github.com/Provenance-Emu/PVLookup; databases fetched at build time via `Scripts/lookup/databases.lock.json`)\n- **PVJIT** — JIT compilation support for emulator cores\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Code Style\n\n### Swift Code\n- Prefer `async/await` over Combine where possible\n- Use `@Sendable` and proper actor isolation for concurrency\n- Avoid force unwraps (`!`) in production code; use `guard let` or `if let`\n- Line length limit: 200 characters (`.swiftlint.yml`)\n- 4-space indentation, no indent for `case` in `switch`\n\n### Emulator Core Bridges (`*Bridge+Controls.mm`)\n- All controller types must be handled: Extended, Micro, Keyboard\n- Button mappings must match upstream emulator constants\n- Never modify upstream submodule source in `Cores/<name>/<upstream-dir>/`\n\n## Common Patterns\n\n### Logging\nUse PVLogging functions — never `print` or `NSLog`:\n```swift\nimport PVLogging\nDLOG(\"Debug: \\(value)\")    // Debug builds only\nILOG(\"Info: \\(message)\")   // Info\nWLOG(\"Warning: \\(msg)\")    // Warning\nELOG(\"Error: \\(error)\")    // Error\nVLOG(\"Verbose detail\")     // Debug builds only (verbose)\n```\n\n### Realm Threading\nRealm objects are thread-confined — never pass them across threads:\n```swift\n// Wrong — crashes across threads:\nlet game = realm.objects(PVGame.self).first!\nTask.detached { _ = game.title } // CRASH\n\n// Right — pass primary key and re-fetch on new thread:\nlet gameID = game.id\nTask.detached {\n    let realm = try await Realm()\n    let game = realm.object(ofType: PVGame.self, forPrimaryKey: gameID)\n}\n\n// Or freeze for read-only cross-thread access:\nlet frozenGame = game.freeze()\n```\n\n### Feature Flags\nGate new features behind `PVFeatureFlags` (Tier 0, importable anywhere):\n```swift\nimport PVFeatureFlags\n\n// Check at runtime (PVFeatureFlags is @MainActor):\nif await PVFeatureFlags.shared.isEnabled(.myFeature) { ... }\n\n// In SwiftUI views via environment:\n@Environment(\\.featureFlags) var featureFlags\nif featureFlags.myFeature { ... }\n```\n\n### Actor Isolation\nUI updates must happen on the main actor:\n```swift\n@MainActor\nclass MyViewModel: ObservableObject { ... }\n\n// From an async context:\nTask { @MainActor in\n    self.isLoading = false\n}\n```\n\n### Platform-Specific Code\n```swift\n#if os(tvOS)\n    // tvOS: no touch input; use Focus Engine and game controllers as primary input\n#elseif os(iOS)\n    // iOS: touch gestures + game controllers\n#endif\n```\n\n## Platform Support Matrix\n\n### Primary Targets (always must compile and work)\n- **iOS 17+** — minimum deployment target\n- **tvOS 17+** — minimum deployment target\n\n### Aspirational Targets (compile where possible, especially non-UI code)\n- **macOS 14+** (Sonoma) — Designed for iPad today; native target planned; Catalyst not supported\n- **watchOS 10+** — limited scope; non-UI utility code only\n- **visionOS 1+** — Aspirational — package declarations + #if os(visionOS) guards only; no app target\n\n### Unit Test Platform\n- **Linux** — Tier 0–2 modules should compile on Linux for CI unit tests via `swift test`\n- Avoid Darwin-only APIs in Tier 0–2 without `#if canImport(Darwin)` guards\n\n### Guards\n```swift\n#if os(iOS)\n    // iOS-only (touch, UIKit specifics)\n#elseif os(tvOS)\n    // tvOS-only (Focus Engine, no touch)\n#elseif os(macOS)\n    // Mac-only\n#elseif os(watchOS)\n    // watchOS-only\n#elseif os(visionOS)\n    // visionOS-only\n#endif\n```\n\n## GitHub Project & Issue Workflow\n\n### Project Board\nAll issues and PRs should be tracked on the [Provenance Roadmap](https://github.com/orgs/Provenance-Emu/projects/1/views/1).\n\n### Labels\nUse these labels consistently:\n- `bug` — regressions or broken behavior\n- `enhancement` — new features or improvements\n- `Epic` — parent issue grouping related sub-tasks\n- `agent-work` — issue assigned to Claude/AI agent lane\n- `cursor-work` — issue assigned to Cursor agent lane\n- `ai-reviewing` — PR currently being reviewed by AI\n- `ai-reviewed` — PR review complete\n- `needs-fixes` — review found issues requiring changes\n- `ready-for-review` — AI review passed, awaiting @JoeMatt\n\n### Epics & Sub-tickets\n- Epic issues use the `Epic` label and list sub-tasks as checkboxes in the body\n- Sub-tasks reference their parent epic with \"Part of #EPIC_NUMBER\" in the PR/issue body\n- Close sub-tasks by merging their PR; epic auto-closes when all checkboxes are checked\n\n### Tagging Reviewers\n- Tag **@JoeMatt** for final human review on any significant change\n- Tag **@copilot** in a PR comment to request a Copilot review pass\n- Tag **@claude** in a PR comment to request a Claude fix/review pass\n- Do NOT tag both simultaneously — one at a time\n\n### WhatsNew / Release Notes\nSee \"## WhatsNew & Release Versioning\" section below for rules.\n\n## WhatsNew & Release Versioning\n\n### CRITICAL: Never Guess Version Numbers\n\nThe `whats-new.json` file (`PVUI/Sources/PVSwiftUI/Resources/whats-new.json`) drives\nthe in-app \"What's New\" sheet. Agents MUST NOT add entries with speculative future version\nnumbers. **The current released version is the latest git tag** — check with:\n```bash\ngit tag --sort=-v:refname | head -1\n```\n\n### Rules for Agents\n1. **Only add a `whats-new.json` entry when**: the PR contains a major user-visible feature AND the target version has been explicitly confirmed in the issue/PR\n2. **Never invent version numbers** — if unsure, skip the entry and note it in the PR body with `TODO: add whats-new entry for version X.Y.Z`\n3. **Version format**: semver string matching the Xcode `MARKETING_VERSION` (e.g. `\"3.3.0\"`)\n4. **Max 6 features** per version entry\n5. **Do NOT modify existing entries** for already-shipped versions\n\n### Changelog Fragments\nPrefer adding `.changelog/<PR_NUMBER>.md` fragments over editing `CHANGELOG.md` directly.\nThe consolidation workflow (`consolidate-changelog.yml`) merges fragments on PR merge.\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are upstream code\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **project.pbxproj** — avoid when possible; prefer SPM Package.swift changes\n\n### What NOT to Flag in Code Review\n- `force_cast` and `force_try` are intentionally warnings, not errors\n- Generated files (`Version.h`, `Version.swift`) — skip review\n- Submodule contents (`Cores/<name>/<upstream>/`) — skip review\n- `project.pbxproj` changes — minimize review noise\n\n### PR Requirements\n- Target the `develop` branch\n- Include test coverage for new logic (where testable)\n- Keep scope focused — one logical change per PR\n- Run `swiftlint` on changed files before submitting\n- Agent PRs should use the `[Agent]` prefix in title\n\n### Branch Naming & Commit Messages\n- Branches: `agent/issue-<N>` for agent work, `feature/<description>` for features\n- Commits: Use conventional commits (`fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`)\n- Keep commit messages concise (< 72 chars for subject line)\n","isInternal":false,"tokens":3391,"sizeBytes":13288},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":204},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":190},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":183}],"systemPromptSnippet":"<yakaai_skills repo=\"Provenance-Emu/Provenance\">\n<!-- File: AGENTS.md (Tokens: ~879 | Category: root-instruction) -->\n# AGENTS.md\n\n## Start here\n- Read `.cursorrules` and `CLAUDE.md` before making changes.\n- Keep scope to one logical change per task.\n- Target the `develop` branch and use conventional commits: `fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`.\n- Prefer the lowest dependency tier that can satisfy the task.\n\n## Project snapshot\n- Provenance is an iOS/tvOS emulator frontend built from many `PV*` Swift packages plus Objective-C / Objective-C++ bridge layers for emulator cores.\n- Main app targets live in `Provenance/`, `ProvenanceTV/`, and `Extensions/`.\n- Emulator bridge work usually lives under each core directory in `Cores/<CoreName>/` or in `CoresRetro/RetroArch/`.\n- Persistence uses Realm and CloudKit. Realm objects are thread-confined; pass object identifiers or frozen objects across threads.\n\n## Hard constraints\n- Do not modify upstream submodule source under `Cores/<name>/<upstream-dir>/`.\n- Do not modify `CodeSigning.xcconfig`.\n- Avoid `project.pbxproj` changes unless there is no viable package-based alternative.\n- Prefer `Package.swift` updates over Xcode project edits whenever possible.\n- Avoid touching generated files such as `Version.h`, `Version.swift`, or build output under generated directories.\n- Preserve existing comments and logic unless the task explicitly requires changing them.\n\n## Module tiers\n- Tier 0: `PVObjCUtils`, `PVFeatureFlags`, `PVCheevos`\n- Tier 1: `PVLogging`, `PVPlists`, `PVHashing`\n- Tier 2: `PVSettings`, `PVPrimitives`\n- Tier 3: `PVSupport`, `PVAudio`, `PVCoreAudio`\n- Tier 4: `PVCoreBridge`, `PVEmulatorCore`, `PVShaders`\n- Tier 5: `PVCoreBridgeRetro`, `PVCoreLoader`, `PVLibrary`\n- Tier 6: `PVUI`, app targets\n\n## Swift and platform conventions\n- Use 4-space indentation and keep lines within the repo's 200-character limit.\n- Prefer `async` / `await` for new asynchronous work.\n- Prefer `guard` / optional binding over force unwraps in production code.\n- When adding or changing Swift APIs, add concise `///` documentation comments where they help future readers.\n- Prefer protocol-oriented designs when they simplify the implementation.\n- Assume Swift changes must work for both iOS and tvOS. Guard unsupported APIs with conditional compilation.\n- Do not add comments that merely restate the code.\n\n## Validation guidance\n- For Tier 0-2 modules, validate locally with `swift build` or `swift test` inside the module when the change is testable.\n- Run `swiftlint lint --path <changed-swift-file>` for changed Swift files when `swiftlint` is available.\n- For higher-tier modules and app targets, validate from `Provenance.xcworkspace`.\n- Start with the `Provenance-Lite` scheme for the fastest Xcode validation. Lite is an internal test build (never published, no users): never release-gate on it or build Lite-only migrations.\n- If an initial Xcode build fails because generated sources are missing, retry once before investigating further.\n- Do not run full iOS/tvOS command-line builds unless the user explicitly asks for them.\n\n## Cursor Cloud specific instructions\n- Cursor Cloud agents here run on Linux, so Xcode, Simulator, and Apple platform runtime validation are unavailable.\n- In this environment, fully validate docs changes and any standalone Swift Package work that can run without Xcode.\n- For workspace-level iOS/tvOS changes that require Xcode, do the best possible static review, run any repo checks that are available, and clearly call out the Mac-only validation that still needs to happen.\n- Never claim an app runtime or simulator change is verified unless it was actually exercised on macOS with Xcode.\n\n## Quick commands\n- `swiftlint lint --path <file>`\n- `cd PV<Module> && swift build`\n- `cd PV<Module> && swift test`\n\n<!-- File: .cursorrules (Tokens: ~388 | Category: cursor-rule) -->\nYou are working on Provenance, a multi-platform emulator frontend for iOS/tvOS.\n\n## Key Rules\n- Read CLAUDE.md for full project conventions, module tiers, and architecture\n- Target the `develop` branch for all PRs\n- Use conventional commits: fix:, feat:, chore:, build:, refactor:, test:, docs:\n- Do NOT modify third-party submodule source in `Cores/<name>/<upstream-dir>/`\n- Do NOT modify project.pbxproj unless absolutely necessary\n- Do NOT modify CodeSigning.xcconfig\n- Prefer SPM Package.swift changes over Xcode project changes\n- Keep changes focused — one logical change per PR\n\n## Swift Conventions\n- 4-space indent, no indent for case in switch\n- Line length: 200 chars max\n- Prefer async/await over Combine for new code\n- Use guard let / if let, avoid force unwraps in production code\n- Realm objects are thread-confined — pass Object IDs across threads\n\n## Build\n- Open Provenance.xcworkspace (not .xcodeproj)\n- Start with Provenance-Lite scheme for fastest builds\n- Initial builds may fail due to lazy code generation — retry once\n- Never build iOS/tvOS from command line unless explicitly asked\n\n## Module Tiers (lowest = fewest dependencies)\n- Tier 0: PVObjCUtils, PVFeatureFlags, PVCheevos\n- Tier 1: PVLogging, PVPlists, PVHashing\n- Tier 2: PVSettings, PVPrimitives\n- Tier 3+: PVSupport, PVAudio, PVCoreBridge, etc.\n- Scope changes to the lowest tier possible\n\n## Testing\n- swift test in standalone SPM modules (Tier 0-2)\n- Xcode workspace build for higher-tier modules\n- Run swiftlint on changed files before submitting\n\n<!-- File: CLAUDE.md (Tokens: ~13003 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.x or Xcode 26+ (use `xcode-select -p` to confirm active version; both are supported)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n- Minimum targets: iOS 17+, tvOS 17+ mandatory. macOS today = \"Designed for iPad\" (the iOS binary on Apple Silicon); a native macOS target is planned (see docs/superpowers/specs/2026-08-07-macos-visionos-strategy-design.md). Mac Catalyst is NOT supported. visionOS/watchOS/Linux: SPM packages declare them — keep code compiling with guards, but no app ships for them.\n\n### Code Signing\nCopy `CodeSigning.xcconfig.sample` to `CodeSigning.xcconfig` and fill in your developer account details.\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\n**Azahar native artifact preparation:** before opening or resolving the shipping workspace, run\n`make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim` for the intended destination).\n`make open` prepares iOS by default. The command reuses the native slice cache and packs its XCFramework;\nSwiftPM owns the wrapper, bridge, Core.plist and migrator test targets. Tuist's `make dev-azahar` performs\nthe same preparation through `dev-generate`. Native engine changes require preparing again before building.\nArm64 simulator builds require `ARCHS=arm64` or `EXCLUDED_ARCHS=x86_64` at the build invocation level.\n\n**Dolphin native artifact preparation:** run `make prepare-dolphin DOLPHIN_SLICE=ios`\n(or `tvos` for a tvOS device) before resolving the shipping workspace. `make open`\nprepares both Azahar and Dolphin. Shipping and Tuist share `PVDolphin-Dynamic`;\nSwiftPM owns the bridge, wrapper, Core.plist, Config and Sys resource bundles.\n`make dev-dolphin` prepares its selected slice before generating Tuist.\n\n**PPSSPP:** shipping and Tuist share `PVPPSSPP-Dynamic`. SwiftPM compiles the native\nengine, its dependencies and the Swift/ObjC++ wrapper, and owns Core.plist and PSP\nassets. No CMake/Xcode project preparation is needed. `make dev-ppsspp` opts into\nthe native focused iOS device app; Thin retains the independent libretro core.\nThe checked-in FFmpeg XCFramework includes arm64 device and simulator slices for\niOS and tvOS. Rebuild it with `Scripts/cores/build_ppsspp_ffmpeg.sh`; the legacy\ncodec headers stay private to PPSSPP.\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores, not really used but should be kept updated regardless\n- Each is a multi-platform 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\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n- **CI cache can shadow source changes.** `actions/cache@v4` caches `Cores/`, `CoresRetro/`, `Dependencies/`, `PVRcheevos/` for submodule speed. A post-restore step runs `git checkout HEAD -- Cores CoresRetro Dependencies PVRcheevos` to un-shadow parent-repo-tracked files. If a CI build ignores your source changes, check that this step ran successfully.\n\n- **xcbeautify hides Run Script output — don't debug CI from the console log.** The build pipes through `2>&1 | tee /tmp/xcodebuild.log | xcbeautify`, and xcbeautify filters out Run Script phase output. `GetModule:` / `MakeFrameworks:` lines therefore never appear in `gh run view` or `gh api .../jobs/<id>/logs`, even when the script ran fine — an empty grep means \"filtered\", not \"never ran\". Those lines exist only in the raw log, which is uploaded on failure as artifact `xcodebuild-log-<IPA_NAME>` (3-day retention):\n\n  ```bash\n  gh run download <run-id> -n xcodebuild-log-Provenance-iOS\n  ```\n\n  The failure-report step also prints, in order: `: error:` diagnostics, the `The following build commands failed:` block, and the last 100 raw lines. It greps `\": (fatal )?error:\"` with a **leading colon** on purpose — a bare `\"error:\"` also matches ObjC selectors like `loadStateToFileAtPath:error:` inside `-Wincomplete-implementation` warnings, which floods the report and buries the real errors. Run Script failures (e.g. \"Generate Frameworks\" exiting non-zero) produce no `error:` line at all and surface *only* in the `build commands failed` block.\n\n- **Symbolicating a crash log.** `release.sh` (local and `testflight.yml`) copies each archive's dSYMs to `build/export/<version>-<build>/dSYMs/<ios|tvos>/` and uploads them to Sentry (`provenance-emu/provenance`, needs `SENTRY_AUTH_TOKEN` or `~/.sentryclirc`; a failed upload only warns). Sentry's crash handler is off, so crashes arrive via MetricKit and on-device `.ips` files. To symbolicate by hand, match the `.ips` binary UUID to a dSYM (`dwarfdump --uuid <x>.dSYM`), then `xcrun atos -arch arm64 -o <x>.dSYM/Contents/Resources/DWARF/<name> -l <load address> <address>` (frames in an embedded framework need that framework's dSYM). Prebuilt binaries — libretro buildbot `*.libretro.framework`, ffmpeg, MoltenVK, Sentry, `PVlibDolphin-ios` — ship no dSYM and stay unsymbolicated.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVAppIntents** — Siri and App intents\n- **PVAudio** — Swift audio apis\n- **PVCheevos** — RetroAchievements API integration\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVFeatureFlags** — Feature flags manager\n- **PVHashing** — ROM file hashing for identification\n- **PVHelp** (separate repo, github.com/Provenance-Emu/PVHelp, shared with iCube) — wiki navigation, page fetch/cache and GitBook preprocessing. App-neutral: Provenance's URLs, page paths and offline SUMMARY.md are `ProvenanceWiki` in PVUIBase (`Resources/WikiContent`)\n- **PVJIT** — JIT compilation support for emulator cores\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVLogging** — Logging infrastructure (CocoaLumberjack-based)\n- **PVLookup** (separate repo, github.com/Provenance-Emu/PVLookup, shared with iFly and iCube) — game metadata, artwork and cheat lookup over libretrodb, OpenVGDB, TheGamesDB, ShiraGame and the libretro cheat database. Ships no database bytes (see \"Lookup databases\" below). Change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVLibrarySnapshot** (separate repo, github.com/Provenance-Emu/PVLibrarySnapshot, shared with iCube and iFly) — the App Group snapshot and cover JPEGs the tvOS Top Shelf reads. Provenance's configuration (app group, `provenance://open?md5=` launch URL) is `PVLibrarySnapshotConfig` in PVAppIntents; PVUIBase `LibrarySnapshotWriter` (tvOS only for now) writes it from `WidgetDataWriter.writeFromRealm()`. Widgets and Quick Look still read `PVLibrarySnapshotLegacy` (`widget.*` keys), whose type names collide with the shared package's: no file may import both\n- **PVNetplay** — Central netplay support code for retroarch and game center\n- **PVPLlists** — Core and System plist processing and other serializer\n- **PVPatching** — ROM/disc patches (IPS, IPS32, BPS, UPS, PPF, xdelta3): import, matching, cache, Patch Applier, patch creation, online catalog\n- **PVPatchEngine** — the streaming patch engine PVPatching applies through (bounded memory, 64-bit offsets, vendored Apache-2.0 xdelta3). Its own repo, github.com/Provenance-Emu/PVPatchEngine, which iCube and iFly also depend on by URL: change it there, tag a release, bump the version in `PVPatching/Package.swift`. Only `PVPatching/Sources/PVPatching/Engine/EngineBridge.swift` may import it (its `PatchFormat`/`PatchError`/`PatchProgress` clash with PVPatching's)\n- **PVPrimitives** — Base data types shared across modules\n- **PVSystems** (separate repo, github.com/Provenance-Emu/PVSystems) — `SystemIdentifier` and per-system metadata (TheGamesDB/libretrodb/OpenVGDB IDs, RetroAchievements console IDs, per-game BIOS manifest). Shared with iCube and iFly: change it there, tag a release, then bump the pin in `Provenance.xcworkspace`'s `Package.resolved` by hand\n- **PVQuicklookSupport** — iOS and macOS Quicklook api support code\n- **PVRcheevos** — RetroAchievements C client integration\n- **PVSettings** — User preferences\n- **PVShaders** — video filters: Provenance's hand-written Metal filters, the bundled libretro slang library (runtime in `PVSlangRuntime`), filter catalog/selection store, previews, non-linear stretch\n- **PVSupport** — Shared utilities\n- **PVThemes** — UI Theming Support\n- **PVTouchOverlay** — Programmatic on-screen controller: layout families, live editor, shared touch primitives\n- **PVUI** — SwiftUI-based shared UI components\n- **PVWebServer** — Swift/Objective-C SwiftPM module for GCDWebServer and WIP new Swift webserver for webdav and http file management, future REST API\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n**Core taxonomy — not every `Cores/PV*` directory is an active shipping core.**\n- **Active native PV* cores** (custom forks or long-supported legacy we\n  actively extend): Azahar, Dolphin, FCEU, Genesis-Plus-GX, Mednafen,\n  Mupen64Plus, ProSystem, snes9x, Stella, plus mGBA, PicoDrive and TGBDual\n  for native-only features. Source of truth:\n  `docs/superpowers/specs/2026-10-10-core-audit.md` (KEEP rows). Retired\n  cores live on as `PVCore.retiredCores` entries (`RetiredCoreMigration`,\n  `RetiredBatterySaveMigration`); `Cores/Debug` stays for its `PVDebug.c` simulator stub; no new `Cores/` project without an audit row.\n- **Deprecated native cores** (`PVCore.deprecatedCores`): Atari800, Bliss,\n  CrabEMU, Gambatte and VisualBoyAdvance-M still ship because they shipped in\n  3.3.x and users have save states on them that the libretro dylibs cannot\n  load, but the libretro cores are the recommended choice. Pickers label them \"(Deprecated)\" and sort them last;\n  nothing migrates away from them. Don't extend them.\n- **Pruned (2026-10, `feature/prune-dead-cores`):** 27 `Cores/` projects that\n  duplicated a libretro buildbot dylib or were never wired were deleted:\n  PokeMini, VirtualJaguar, O2EM, Desmume2015, melonDS, BeetlePSX, DosBox, DuckStation,\n  FreeIntv, GameMusicEmu, Gearcoleco, JollyGoodEmulation, Mini_vMac, Mu,\n  Mupen64Plus-NX, Play, Potator, Reicast, Sudachi, VecX, Yabause, fuse, opera,\n  pcsx_rearmed, sm64ex, snesticle, supergrafx. Their games run on the thin\n  wrapper through the replacement table in `PVCore.swift` where a replacement exists (17\n  entries); cores that were disabled or never shipped have no entry.\n- **Rule of thumb:** if a libretro buildbot dylib already serves the\n  core and the thin wrapper handles it, fix the thin wrapper (or the\n  upstream dylib via Provenance fork), not a native PV* shell.\n  The whole point of the thin wrapper is to make those cores feel\n  basically native without per-core PV* maintenance.\n\n**Wrapper platform defaults — patch the right file:**\n- **Both iOS and tvOS** now default to the THIN libretro wrapper\n  (`PVCoreBridgeRetro/Sources/PVLibRetro/PVThinLibretroFrontend.mm`\n  + `PVThinLibretroCore*.swift`). dlopen + function pointers against\n  the libretro buildbot dylibs; no full RetroArch runtime.\n- **PPSSPP runs on the THIN wrapper, on Vulkan** (no force-route since `2f09fd7802`, 2026-05-28). On GLES, PPSSPP spawns its own render thread (`useEmuThread = GPUCORE_GLES`) that never has a current EAGL context, so GL can't work on thin; `GET_PREFERRED_HW_RENDER` returns Vulkan for PSP and GL is rejected (`e66b2a28a4`, `79a091c4b2`), with `context_reset` deferred until after `retro_load_game`. A failed deferred Vulkan setup now fails the load, and fence waits are bounded (`f87b9d17e1`).\n- **Thin `rendersToOpenGL` is true for every HW-render core, Vulkan included.** `PVThinLibretroFrontend` returns `_hwRenderRequested`, so a Vulkan core takes the \"OpenGL\" branch of `PVMetalViewController.draw(in:)` and never reaches the software-upload path. `rendersToVulkan` is the one that distinguishes them.\n- **Thin core options are replayed at boot, not read on demand.** The frontend holds option values only in memory; `PVThinLibretroCore.applyPersistedCoreOptions()` pushes the saved ones in from `afterCoreInitBlock` (after `retro_init`, before `retro_load_game`). The UI stores a switch state or a choice *label*; `ThinCoreOptionDefinition.rawValue(forStored:)` maps it to the core's value. A replay that never finishes booting is skipped once on the next launch.\n- The thick RetroArch wrapper (full in-process RetroArch, `PVRetroArch.framework`) has been removed; every libretro core runs on thin. What remains under `CoresRetro/RetroArch/` serves thin: `Core.plist` (the libretro core list, bundled via the `PVCoreLoader` resource symlink `RetroArchCore.plist`), `scripts/` (dylib download/packaging, `cores.yml`), `modules/` and `libretro-super`. A core from that list is available (`PVCore.hasCoreClass`) only when its `<name>.libretro.framework` is in the app bundle. Its top-level `com.provenance.core.retroarch` entry (the old launcher) is `PVDisabled`; its `PVRetroArch.PVRetroArchCoreCore` principleClass strings stay because `PVCoreFactory` keys on them.\n- **Thin netplay is libretro netpacket (env 78) only** — cores that register it (gpSP, melonDS DS) get netplay; RetroArch rollback netplay went with the RetroArch runtime. `NetpacketTransport` (PVNetplay) carries packets over framed TCP; `PVThinLibretroFrontend` hands every netpacket callback to the core on the emulation thread from a per-frame pump before `retro_run`, never from the network queue. Hosts advertise Bonjour `_provenance-np._tcp`, which must stay listed in every app Info.plist's `NSBonjourServices`. Cores with their own netplay socket (Dolphin ENet, mGBA, Mednafen, PPSSPP ad hoc) set `netplayAdvertisesOwnRoom = false` (the default) and `PVNetplayManager.host(settings:game:)` publishes the room for them (`NetServiceRoomAdvertiser`); TXT keys live in `NetplayRoomTXTRecord`. Without that, a LAN room is invisible to Browse.\n- **Thin wrapper gaps:** deferred Vulkan context setup (GL is now eagerly activated; Vulkan still deferred). BIOS files synced from `BIOSPath` into system directory before `retro_load_game`. `ScalingMode` integration works (via `PVMetalViewController` + DeltaSkin skin container). Fast-forward wired (`setGameSpeed:` override syncs `_speedMultiplier` + `_audioPaused`).\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access (see DEVELOPER.md for patterns)\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying third-party upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n### Build & toolchain gotchas\n\n- **Most workspace `.xcodeproj`s list sources explicitly** (e.g. the remaining native core projects), so a new file compiles in SwiftPM yet breaks the archive build. `Scripts/audits/check_pbxproj_sources.py` (CI: `xcode-project-sources.yml`) flags a source file sitting beside compiled ones that its project doesn't reference.\n- **Every script under `Scripts/` or `CoresRetro/RetroArch/scripts/` must be in `Scripts/maint/jobs.toml`** (as a job, or under `[ignore]`); `maint.yml` fails PRs that add an unregistered one. `make maint` (dashboard) / `python3 Scripts/maint/maint.py status` show which generated files are stale. Scripts that find the repo root from their own path sit two levels deep (`Scripts/<category>/`).\n- **PVUI cannot build inside a git worktree.** The PackageBuildInfo plugin reads `.git/HEAD`; a worktree's `.git` is a file. Run PVUI tests from an rsync'd copy with a fake `.git/HEAD` and submodule symlinks (see the Phase 1 overlay plan, Global Constraints).\n- **`gh issue list` has no `--sort` flag.** Use `gh issue list --search \"sort:created-desc\"` or `gh issue list --json number,title,createdAt --jq '.'` for sorted/filtered queries.\n\n### Dev workspace (Tuist)\n\nA second, generated workspace for fast iteration. `Provenance.xcodeproj` still ships\n(CI, fastlane, release.sh). Spec: `docs/superpowers/specs/2026-10-10-dev-workspace-design.md`.\n\n- **Generate / open:** Tuist 4.200.0 is pinned in `.mise.toml`. Run `mise exec -- tuist generate --no-open`\n  (or `make dev-generate`) from the repo root; `make dev` also opens `Provenance-Dev.xcworkspace`.\n  Generated files (`Provenance-Dev.xcworkspace`, `Dev/Provenance.xcodeproj`, `Dev/Derived`) are\n  gitignored. Never commit them. The Tuist project is named `Provenance` and lives in `Dev/`\n  because `Build.xcconfig` derives bundle ids, the app group and the iCloud container from\n  `$(PROJECT_NAME:lower)`.\n- **Focused apps:**\n  - `Provenance-Dev-UI`: mGBA, Stella, snes9x.\n  - `Provenance-Dev-Thin`: no native cores; 4 libretro dylibs (mednafen_psx_hw, mupen64plus_next, snes9x, ppsspp).\n  - `Provenance-Dev-Azahar`: Azahar.\n- `Provenance-Dev-EmuThree` (opt-in, iOS device): `make dev-emuthree` uses the same\n  `PVEmuThree-Dynamic` SwiftPM product as shipping. The package compiles the legacy engine\n  and bridge sources and selects the arm64 iOS/tvOS device or simulator vendor slice;\n  no core Xcode project is needed. Rebuild those slices with\n  `python3 Scripts/cores/build_emuthree_vendors.py`.\n  - `Provenance-Dev-Dolphin` (opt-in, device only): `Cores/Dolphin/Package.swift` wraps\n    `PVlibDolphin.xcframework` as a `.binaryTarget`. SwiftPM will not load the package until\n    `build_slice.py dolphin <slice>` has produced the xcframework, so `make dev-dolphin` runs it first\n    and sets `TUIST_DEV_DOLPHIN=1`; without that variable the app and package are not in the generated\n    project (CI never sets it). The ObjC++ target reaches the Swift module through the runtime\n    (`#if SWIFT_PACKAGE` in `PVDolphinCore.mm`), because SwiftPM can't build the mixed target as one.\n\n  Build with `make dev-ui|dev-azahar|dev-thin` (derived data in `build/dev-dd`; override with\n  `DEV_DERIVED=`). Each app is one iOS+tvOS target with bundle id\n  `org.provenance-emu.provenance.dev.<slug>`.\n- **Simulator builds are ad-hoc signed by `Dev/Config/Dev.xcconfig`.** Never pass\n  `CODE_SIGNING_ALLOWED=NO` to a dev build: the app then dies in dyld on the unsigned embedded\n  MoltenVK.\n- **Dev apps are arm64-only; Intel is unsupported** (decided 2026-10-12: no Intel Mac runs a\n  supported Xcode). `DevSettings` sets `EXCLUDED_ARCHS[sdk=*simulator*] = x86_64` for every focused\n  app, and the Makefile, harness and both CI workflows also pass `EXCLUDED_ARCHS=x86_64` to\n  xcodebuild, because a project setting never reaches package targets (CI kept compiling x86_64\n  for every package without it). Prebuilt cores need no x86_64 slice and SwiftPM core targets need\n  no per-arch source lists. Don't add x86_64 workarounds (stubs, generic-source fallbacks) here.\n- **The dev workspace has no committed Package.resolved, so it resolves the newest remote\n  packages.** Anything that only works because `Provenance.xcworkspace`'s resolved file pins an\n  older version breaks here first. Known case: Hummingbird imports `ServiceContextModule` without\n  declaring it, and swift-service-context 1.4.0 / swift-distributed-tracing 1.6.0 (2026-10-12)\n  dropped it from `Tracing`'s dependency chain, so builds failed at random with \"no such module\n  'ServiceContextModule'\"; `PVWebServer/Package.swift` caps both packages. Fix such breaks with a\n  version cap in the declaring package, not with xcodebuild flags.\n- **Helpers:** `Tuist/ProjectDescriptionHelpers/` holds `CoreProduct` (the core table),\n  `FocusedApp` (the target template plus `FocusedApp.all`), `LibretroCores` and `DevSettings`. Tuist\n  only finds helpers at `<root>/Tuist/ProjectDescriptionHelpers`; don't add a `Dev/Tuist/`.\n- **How cores get in:**\n  - SwiftPM dynamic cores are embedded with `.package(product:, type: .runtimeEmbedded)`.\n  - Cores from a hand-maintained `Cores/<X>/*.xcodeproj` are linked by `-framework <Product>` in `OTHER_LDFLAGS`.\n    That project is referenced from `Workspace.swift` (Xcode then builds it as an implicit dependency),\n    and the \"Embed core frameworks\" post script copies and signs the framework. Tuist can't\n    depend on targets of projects it doesn't generate.\n- **Libretro dylibs per app:**\n  - `FocusedApp.libretro` names (cores.yml) become a pre script calling\n    `CoresRetro/RetroArch/scripts/get-modules.sh --urls <file>`, plus a post script running\n    `make_frameworks_retroarch.sh` and `validate_frameworks.sh`.\n  - `--urls` keeps its own state dir (`modules_compressed/<iOS|tvOS>-urls-<sha12>`) and never prunes the shared `modules/`.\n  - The buildbot dylibs are iOS-platform binaries and cannot `dlopen` in a simulator, so\n    **Provenance-Dev-Thin plays games on a device only**.\n- **Manifest checks:** `Scripts/dev/check_dev_manifest.sh` compiles the helpers against Tuist's\n  `ProjectDescription.framework` and checks three things: every libretro name is enabled in cores.yml,\n  every path exists, and no local package is declared twice. Prebuilt artefacts (e.g. `PVlibDolphin.xcframework`) are\n  required only for rows a `FocusedApp` uses.\n- **Harness:** `make dev-harness ROM=<path> TARGET=ui|azahar FRAMES=n CORE=<id> SIM_DEVICE=<udid|booted>`\n  builds the app, launches it with the `-PVHarnessROM`, `-PVHarnessCore`, `-PVHarnessFrames`,\n  `-PVHarnessOut` and `-PVHarnessExit` launch arguments, and copies `screenshot.png`, `frames.json`, `log.txt` (or\n  `error.txt`) to `build/harness/<Scheme>/`. `TARGET=thin` is refused (exit 2): Thin runs on a device only.\n  Synthetic test ROMs come from `Scripts/dev/make_harness_rom.py` (2600 and gba). Frame counts are\n  time-based estimates (`\"frameCountSource\": \"estimated\"`). Its first catch was a native Stella VFS\n  regression (`16da1e4c40`).\n- **Core slices:** `Scripts/cores/build_slice.py <azahar|dolphin> <ios|ios-sim|tvos|tvos-sim>\n  [--print-key|--force|--xcframework|--cache-dir DIR]` builds or links one slice.\n  - The key is a content hash: submodule working tree (dirty edits bust the cache; `--force` rebuilds) and\n    nested externals, build script and toolchain, flags, Xcode and SDK, and Azahar's MoltenVK slice.\n  - Dolphin keys only on the dolphin-ios paths its CMake build reads (`DOLPHIN_BUILD_PATHS`: top-level\n    CMake files, `Externals`, `Source/Core`, `Source/iOS/Library`, `ApprovedInis.json`), as HEAD tree\n    hashes plus dirty edits under them. iCube app commits and edits there no longer force a rebuild;\n    if the build starts reading a new path, add it to that list.\n  - The cache is `$PV_CORE_CACHE` or `~/Library/Caches/Provenance/cores/<core>/<slice>/<key12>/`.\n  - On a hit, the legacy `build/xcframework/<Product>-<slice>.framework` path is a symlink into it.\n  - Only `--xcframework` repacks the multi-slice xcframework (Dolphin's is repacked after every miss).\n    If its hard links fail, set `PV_CORE_CACHE` to a folder on the repo's volume.\n- **CI:** `build.yml` and `testflight.yml` cache `~/Library/Caches/Provenance/cores` per platform,\n  keyed on `build_slice.py --print-key`. `dev-workspace.yml` smoke-builds `Provenance-Dev-UI` on\n  both simulators (Tuist via mise, which needs `GITHUB_TOKEN`).\n- **Pruning rule:** no new `Cores/` project without an audit row in\n  `docs/superpowers/specs/2026-10-10-core-audit.md`.\n\n### Azahar core build gotchas\n\n- **Shipping Xcode and Tuist consume `PVAzahar-Dynamic` from `Cores/Azahar/Package.swift`.** Prepare its cached CMake-built `PVlibAzahar.xcframework` before package resolution: `make prepare-azahar AZAHAR_SLICE=ios` (or `ios-sim`, `tvos`, `tvos-sim`). `make open`, dev generation, CI and release entry points do this automatically. A cold slice takes ~30–40 min and needs cmake, ninja and python3 ≥ 3.10. The content key includes the submodule/externals, builder, toolchain, Xcode/SDK and MoltenVK slice; a hit reuses native output and refreshes `build/headers`. Force a rebuild with `build_slice.py azahar <slice> --force --xcframework`.\n- **The package graph always declares the native binary.** Missing output must be prepared before resolving; a build phase cannot produce an XCFramework early enough. The package's ObjC++ bridge (`PVAzaharBridge`) must never import the Swift module (`PVAzahar-Swift.h`): that is a target cycle. The Swift core pushes `renderViewClass` and `userDirectoryPath` into it from `init`, and runs its per-load setup (options, `respectsMuteSwitch`, emuThreeDS import) from the bridge's `willLoadFile` hook. A `loadFile` override on a Swift core never runs: `PVEmulatorViewController.createEmulator` calls `bridge.loadFile` for every `ObjCBridgedCore` (and `PVEmulatorCore.loadFile` only throws), which is how 3.4 TestFlight shipped \"Azahar has no user directory\".\n- **PVlibAzahar is arm64-only, and so is every simulator build.** `PVAzahar` excludes x86_64 simulator slices, as do `Build.xcconfig` (the shipping app) and the dev apps; the old link-only x86_64 stub is gone. Package targets ignore project settings, so generic-simulator builds also pass `EXCLUDED_ARCHS=x86_64` to xcodebuild.\n- **Never include azahar's `core/hle/service/nwm/nwm_uds.h`.** Azahar headers come in with `-idirafter`, so its `#include \"network/network.h\"` resolves to the SDK's Network framework header instead. The bridge includes azahar's own copy as `\"network.h\"` via a narrow `HEADER_SEARCH_PATHS` entry.\n- **Emulator changes go on the fork.** Commit them to `Provenance-Emu/azahar` branch `provenance` and list them in `Cores/Azahar/PATCHES.md`. Never edit `Cores/Azahar/azahar` in place.\n- **The tvOS user root is Caches.** `PVAzaharCore.userRootURL` is `Documents/Azahar` on iOS and `Library/Caches/Azahar` on tvOS, where Documents is not writable.\n- **`-fcxx-modules` is on** so the ObjC++ glue can `@import` its PV* dependencies. It must not import `PVAzahar-Swift.h`.\n- **`runOnEmuThreadAndWait:` jobs can outlive their timeout.** Capture only by value or `shared_ptr`, never by reference.\n- **PVAzahar links with `-unexported_symbols_list unexported_symbols.txt` (`__Z*`).** dyld coalesces *exported* weak C++ definitions (template instantiations, inline functions) across every loaded image. PVlibDolphin loads first and exports its own glslang and fmt instantiations from different library versions, and azahar's shader compiler crashed in glslang's parser after calling Dolphin's `addSwizzle`. Keep the C++ surface private; apply the same to any new core framework that static-links glslang, fmt, SPIRV-Cross or similar. PVEmuThree uses the same list. PVlibDolphin cannot (the PVDolphin bridge imports Dolphin's C++ API, including weak `MMU::HostRead<>` instantiations), so dolphin-ios's `BuildiOSXCFramework.py` relinks a second time with a generated list of third-party weak symbols via `-DDOLPHIN_UNEXPORTED_SYMBOLS_LIST`; the global `CMAKE_SHARED_LINKER_FLAGS` never reaches the `dolphin` target. Audit a built app with `nm -m <fw> | grep 'weak external'` per framework and `comm -12` the sorted lists.\n- **Keep C++ visibility filtering at the final framework link.** Compile-time hidden visibility lets Xcode's SwiftPM `ld -r` step localize bridge definitions before the Swift module and engine can resolve them. `Package.swift` forwards `-ObjC` with `-Xlinker` for the Swift link driver; `unexported_symbols.txt` hides C++ symbols in the final image.\n\n### Metal rendering gotchas\n\n- **`CAMetalLayer.nextDrawable` blocks forever when backgrounded.** Metal reclaims drawables during background. The CA runloop observer fires `MTKView.draw(in:)` before `didBecomeActiveNotification`, so `currentDrawable` deadlocks the main thread. ALL `draw(in:)` implementations MUST early-return when `UIApplication.shared.applicationState != .active`.\n- **`@synchronized(self)` in emulation loop vs main thread.** The emu loop holds `@synchronized(self)` around `executeFrame` at 60fps. Any main-thread code that acquires the same lock (e.g., `setPauseEmulation`) will starve. Set atomic flags BEFORE acquiring the lock so the emu loop sees the change and releases.\n- **Thin wrapper reports its geometry/aspect/sample-rate LATE.** `PVThinLibretroFrontend` populates `_rawAVInfo` (geometry, aspect, `timing.sample_rate`, fps) only at the END of `startWithROMPath` — which runs inside `startEmulation`, NOT during `loadFile` (that only dlopens the core). But `createEmulator()` sets up the audio graph (`setupAudioGraph`/`startAudio`) AND the DeltaSkin viewport BEFORE `core.startEmulation()`. So anything that reads the core's geometry/aspect/sample-rate at view/audio-setup time gets the fallback defaults (`?: 640/480/256`, `audioSampleRate → 44100`), not the real values. Native/thick cores populate av_info during `loadFile`, so they don't hit this. This has caused: (1) thin-wrapper audio built at 44100 then never rebuilt → bassy/glitchy (fixed by rebuilding the audio graph after start if the rate changed, #cb40fef061); (2) skin render view stuck full-screen/wrong-size until rotation because the viewport was computed before geometry settled (fixed by recomputing on layout-settle, #a63e1622f1). Pattern for any new feature that reads core geometry/rate: recompute/refresh AFTER `startEmulation`, gated on the value actually having changed.\n\n### Shader / filter system\n\n- **The slang library is fetched at build time, never committed.** `Scripts/shaders/fetch_slang_shaders.py` downloads the release pinned in `Scripts/shaders/slang-shaders.lock.json` (JoeMatt/slang-shaders-metal, `.oecompiledshader` zips compiled from libretro slang by OpenEmu-Shaders' `oeshaders`), unpacks each preset into a `shader.json` directory (no runtime unzip), merges the hand-written presets in `PVShaders/Library/native-metal/`, and writes `manifest.json`. Every app target (shipping xcodeproj and Tuist `FocusedApp`, `slangShaders:`) copies it into the bundle as `SlangShaders/` via `embed_slang_shaders.sh`. To bump the snapshot: change the lock, run `Scripts/shaders/validate_slang_shaders.sh` (renders every preset on the Mac GPU, writes `slang-shaders-excluded.json`), commit both.\n- **Which filter draws is per game.** `ShaderLibraryStore` (PVShaders) holds selections for global / `system:<id>` / `game:<md5>` scopes (game wins), parameter overrides, favorites and recents; selections, parameters and favorites sync through iCloud KVS as timestamped per-key maps (`StampedMap`, merged both ways via `iCloudSettingsSync.register`). Native filters keep their values in their old `Defaults` keys. The legacy `metalFilterMode` key is still bridged into the global selection; its raw values are case names (`Always(vhs)`, not `Always(VHS)`).\n- **`ShaderSelection.off`, not `.none`.** A `.none` case on an enum passed as `ShaderSelection?` silently becomes `Optional.none` and clears the scope.\n- **Renderers:** `PVMetalViewController` resolves the filter every frame (native → `PVMetalFilterRenderer`, library → `SlangFilterRenderer`, which compiles presets off the main thread and draws the plain picture until ready). Cores that present from their own thread (Dolphin Metal backend, native PPSSPP) use `ShaderPostProcessor`, fed a resolved snapshot on `ShaderLibraryStore.didChangeNotification`. Dual-screen layouts and Azahar are never filtered (pause tiles hidden via `filtersApplyToCurrentLayout`); `LCDScreenFilter` (CIFilter) is the intended DS/3DS look.\n- **Filters draw into the picture rect, not the drawable.** Until a skin reports its screen rect the GPU view is full screen; `RenderGeometry.contentRect` letterboxes the picture inside it. Code that draws over the frame must use the same rect (this was the \"VHS covers the whole screen\" bug).\n- **`swift test` in PVShaders runs real Metal tests on the host GPU**, but the SwiftPM CLI only copies `.metal` files: the slang runtime compiles its bundled source as a fallback, while native-filter pipelines (package metallib) exist only in Xcode builds, so those tests skip.\n\n### Lookup databases (PVLookup)\n\n- **The databases are fetched at build time, never committed.** `Scripts/lookup/databases.lock.json` pins five assets by sha256 on a `databases-YYYY-MM-DD` release of Provenance-Emu/PVLookup. Every app target (shipping xcodeproj \"Script: Embed Lookup Databases\", Tuist `DevSettings.embedLookupDatabasesScript`) runs `embed_lookup_databases.sh`, which fetches them into the gitignored `LookupDatabases/` (cache: `~/Library/Caches/PVLookupDatabases`) and copies the zips to the bundle ROOT, where PVLookup's default `LookupDatabases` source looks for `<file name>.zip` in `Bundle.main`. No `LookupDatabases.configure` call is needed. `make lookup-databases` fetches by hand.\n- **`fetch-lookup-databases.sh` is a copy of PVLookup's canonical script**; keep it identical. To bump the databases: publish a new release in the PVLookup repo, then update the lock file.\n- **The cheat database generator still lives here.** `make update-cheatdb` writes `build/cheatdb/libretro_cheats.sqlite.zip` for publishing as a PVLookup release asset; the app never builds from it.\n- **PVLookup logs through `PVLookupLog`**, routed into PVLogging by `LoggingBootstrapTask` on a serial queue: PVLogging's `log()` holds four `Logger` interpolations, and calling it at the bottom of a deep lookup overflowed a 512 KB concurrency stack in a Debug build (iFly).\n- **`PVLookupTypes` and `TheGamesDB` are not products.** Their imports in PVLibrary/PVUI resolve through the `PVLookup` product's transitive modules, as they always did.\n\n### Debugging emulator cores\n\n- **\"Black screen\" reports: ask for the `[NO-FRAME]` log line.** Five seconds after a successful boot with nothing presented, `PVEmulatorViewController+FirstFrameWatchdog` logs the presenter, render path, pause state, geometry and the GPU view's frame/visibility. Cores that draw to their own view (native Dolphin, PPSSPP, RetroArch `skipLayout`) trip it by design.\n- **Per-game core options depend on `CoreOptionsContext.currentGameMD5`.** It is set in `initCore()` before the core initializes and cleared in `quit`; `CoreOptional.currentGameMD5` defaults to it. Without it a core reads only the core-wide key and per-game overrides are silently ignored.\n- **flycast cannot be debugged with Xcode attached.** It installs `signal(SIGSEGV, ...)` for VRAM lazy-mapping; Xcode catches SIGSEGV and pauses, breaking the core. Use Console.app (filter `Process = Provenance`) for live logs OR `iOS Settings → Privacy & Security → Analytics → Analytics Data` for `.ips` crash files post-mortem.\n- **Sentry's `enableCrashHandler` is disabled** at `SentryBootstrapTask.swift:48` because its SIGSEGV handler conflicts with flycast's MMU path. Do NOT re-enable it. Crash telemetry flows through MetricKit instead.\n- **iPad MoltenVK surface_caps lie.** On iPadOS 26 with Stage Manager / adaptive scaling, `VkSurfaceCapabilitiesKHR.currentExtent` / `minImageExtent` / `maxImageExtent` ALL report `view.bounds × contentScaleFactor`, NOT the actual `CAMetalLayer.drawableSize`. They can differ (e.g. 2732×2048 vs 2092×1568). The authoritative source for iOS Vulkan is `metalLayer.drawableSize`. Never clamp against MoltenVK surface caps on iOS.\n- **libretro core option key prefixes don't always match the core name.** flycast's options are `reicast_*` (per `CORE_OPTION_NAME` in `shell/libretro/libretro_core_option_defines.h`). Always grep the core's `libretro_core_options.h` before guessing key names.\n- **Jaguar numpad uses keyboard input.** The upstream `virtualjaguar-libretro` core reads numpad buttons 0-9/*/# via `RETRO_DEVICE_KEYBOARD` (`RETROK_0`-`RETROK_9`, `RETROK_MINUS`, `RETROK_EQUALS`), NOT joypad buttons. Buttons 7-9/*/# have NO joypad mapping. The thin wrapper dispatches both keyboard events (via `_bridge.setKeyState`) and joypad presses for compatibility.\n\n### Code conventions\n\n- **Library storage: `LibraryLocation` owns the roots; never move library folders by hand.** `URL.documentsPath`/`cachesPath` and `LibraryLocation.current` share one launch-fixed precedence (stored `libraryLocation` → legacy `useAppGroups`, both snapshotted at launch). Flipping `useAppGroups` alone no longer moves files.\n  - Moves go through `LibraryMoveCoordinator` (PVFileSystem `LibraryMove/`): journaled in the app's Application Support, verified (size; MD5 under `ROMs/`+`BIOS/`), switched by writing the setting, and cleaned up on the *next* launch after a delta sweep.\n  - Leaving the App Group moves every top-level entry except hidden files, `.realm` names and the journal, including entries created after the move started. \"Identical\" means size **and** MD5, in every folder. A copy-phase conflict moves the destination's own file aside to `Conflicts/` and puts the live file at the real path; the post-switch sweep files a changed source under `Conflicts/`. Nothing is ever deleted or overwritten, and copies go through a hidden temp file and a rename.\n  - \"RESET TO DEFAULT\" must not touch `libraryLocation`/`useAppGroups`. Lite and the full app share the group, so a move keeps a copy while the other app still uses it.\n  - A folder picked in Files is `LibraryLocation.pickedFolder`: the security-scoped bookmark is `Defaults[.libraryPickedFolder]`, resolved once per launch by `LibraryLocation.pickedFolderAtLaunch` (never stopped; a failure is `.unavailable` and the UI offers re-pick / App storage / retry). It means two roots: `rootURL` for `ROMs/`, `BIOS/`, `System/` and `savesRootURL` for the five saves folders, which join the picked folder only when `includesSaves` is on. `LibraryFolderInspector` gates every pick (writable, not the library itself, not inside a moved folder). The journal is version 2 (`sourceFolder`, `destinationFolder`, `deduplicateByMD5`, `remaps`).\n  - Joining a folder that already has `.provenance-library.json` de-duplicates `ROMs/` and `BIOS/` by size + MD5 under any name; the resulting `remaps` must be applied to the database before cleanup deletes sources, so a remap applier (`LibraryPathRemapper`) has to be registered before `resumeIfNeeded()` (`PVGameLibrary.init` does this). iCloud-located picked folders are watched by `PickedFolderCloudWatcher`.\n- **Changing a game's MD5 goes through `GameRekey`, never a delete.** `PVGame.md5Hash` is the Realm primary key, so a ROM rewritten in place (\"Replace the Original\") needs a new row: `GameRekey.rekey(gameWithMD5:to:crc:fileSize:in:)` copies the game (keeping `id`), re-points saves, cheats, recents, patches, libraries and controller profiles, then raw-deletes the old row. `RomDatabase.delete(game:)` would delete the ROM file, saves and patches. The library MD5 is the importer's (`GameImporterDatabaseService.calculateMD5(forGame:)`, header-aware), not the raw file MD5. Replacing is blocked while iCloud Sync is on: another device's soft-delete of the old game removes the file at the shared path.\n- **Notification names — no magic strings.** Declare `FOUNDATION_EXPORT NSNotificationName const FooBarNotification;` in an ObjC `.h`, define in the matching `.mm`, mirror via `Notification.Name` extension in Swift referencing the same string. Posters reference the const; observers reference `Notification.Name.fooBar`. Single string literal lives in one `.mm` — never at a call site.\n- **Email in public-facing files / comments:** use `git@joemattiello.com` (SECURITY.md, README, GitHub issue comments, anything that ships publicly).\n- **ObjC categories in SwiftPM modules can be silently elided.** When a category is declared in a separate `.h` file referenced by a modulemap, Swift module synthesis may drop it entirely — no error, just \"no member\" at call sites. Workaround: declare methods in the main `@interface` block. Known to affect `(LightGun)` categories on PVStellaBridge and PVProSystemGameCore. Other categories (Cheats, Trackball) in the same files work fine — root cause unclear.\n\n### Subagent worktree rules\n\n- Subagents in `isolation: worktree` MUST stay on their own branch — never `git reset` / `rebase` / `push` / touch `develop`. Their commits get cherry-picked onto develop afterward.\n- Always include \"DO NOT git reset / rebase / push / touch develop\" in subagent prompts. Without it, parallel agents have historically collided (one agent ran `git reset HEAD~1` and dropped another agent's commit).\n- After an agent reports completion: cherry-pick from the agent's branch onto develop in the MAIN worktree's `Provenance/` directory. Shell can land in the agent's worktree if you're not careful — verify `pwd` and `git branch --show-current` before each cherry-pick.\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# PVUI unit tests (PVUIBaseTests + PVSwiftUITests). Use the committed\n# PVUI-UnitTests scheme, not the auto-generated PVUI one: that also builds the\n# snapshot suite, whose Prefire-generated code doesn't compile. On Xcode 26.6\n# realm-core needs `-xcconfig` with OTHER_CFLAGS/OTHER_CPLUSPLUSFLAGS =\n# $(inherited) -Wno-invalid-specialization (CI's Xcode 26.3 doesn't).\ncd PVUI && xcodebuild test -scheme PVUI-UnitTests \\\n  -destination 'platform=iOS Simulator,name=iPhone 17' \\\n  CODE_SIGNING_ALLOWED=NO -skipPackagePluginValidation -skipMacroValidation\n\n# PVLibrary tests: the committed PVLibrary-UnitTests scheme, through the\n# package workspace (PVLibrary/PVLibrary.xcodeproj shadows Package.swift, and\n# the auto-generated scheme has no test action). Same realm-core -xcconfig on\n# Xcode 26.6. CI: pvlibrary-tests.yml (develop pushes + PRs via agent-validation).\ncd PVLibrary && xcodebuild test -workspace .swiftpm/xcode/package.xcworkspace \\\n  -scheme PVLibrary-UnitTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  -collect-test-diagnostics never CODE_SIGNING_ALLOWED=NO \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# PVCoreBridgeRetro (thin wrapper) tests: PVLibRetroTests in Package.swift.\n# Open the package in Xcode and select its test scheme, or use its package workspace.\n# Ad-hoc signing is required for MoltenVK.\nxcodebuild test -workspace PVCoreBridgeRetro/.swiftpm/xcode/package.xcworkspace -scheme PVCoreBridgeRetro \\\n  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \\\n  CODE_SIGN_IDENTITY=- CODE_SIGN_STYLE=Manual DEVELOPMENT_TEAM= \\\n  -skipPackagePluginValidation -skipMacroValidation\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Agents should scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode (PVShaders: `swift test` works) |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **GitHub workflow files** — `.github/workflows/*.yml` may be edited when fixing CI issues or optimizing builds. However, GitHub Actions bot PRs cannot push workflow changes (requires `workflows` permission) — if an agent PR needs to include workflow changes, note this in the PR description so a maintainer can push them manually.\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are third-party upstream code (do not casually fork in place)\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **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. Use `PBXFileSystemSynchronizedRootGroup` for source directories (Xcode 16+). Prefer minimal diffs — only touch the sections that need changing.\n\n### Minimum Deployment Targets\n\nProvenance ships for **iOS 17+ and tvOS 17+**. The PV* packages also declare **macOS 14+ and visionOS 1+** so keep code compiling for them with platform guards, but no Catalyst/native-mac/visionOS app target ships today (native macOS is planned — see the 2026-08-07 macOS strategy spec). 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.\n\n**Prefer modern Swift/SwiftUI APIs** when the minimum deployment target supports them:\n\n| Prefer (iOS 16+/17+) | Over (older) |\n|---|---|\n| `ShareLink` | `UIActivityViewController` wrapped in `UIViewControllerRepresentable` |\n| `@Observable` macro (iOS 17+) | `@ObservableObject` + `@Published` |\n| `NavigationStack` | `NavigationView` |\n| `.navigationBarTitleDisplayMode(.inline)` on non-tvOS | conditional guard |\n| `UIWindowScene.keyWindow` | `UIApplication.shared.keyWindow` (deprecated iOS 13) |\n\n**`@StateObject` vs `@ObservedObject` rule:**\n- `@StateObject` — use only when the view **creates and owns** the object's lifetime (new instances).\n- `@ObservedObject` — use for **singletons** (e.g. `Foo.shared`) and objects passed in from outside.\n  Using `@StateObject` with a singleton is semantically wrong even though it compiles.\n\n### Pre-PR Validation (MANDATORY)\nAgents MUST run these checks before creating a PR. Do NOT skip any step.\n\n1. **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).\n\n2. **Lint** — Run `swiftlint lint --path <file>` on every changed Swift file.\n\n3. **Unit tests** — If the module has tests, run them: `cd PV<Module> && swift test`. If adding new logic, add test coverage.\n\n4. **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.\n\n5. **No magic numbers** — Extract constants. Don't hardcode values that are used in multiple places.\n\n6. **Use `SystemIdenti\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~3391 | Category: copilot-instructions) -->\n# Copilot Instructions for Provenance\n\n## Project Overview\n\nProvenance 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.\n\n## Build & Development\n\n### Prerequisites\n- Xcode 16.2 (`/Applications/Xcode_16.2.app`)\n- Ruby + Bundler (for fastlane)\n- `make setup` to install all dependencies\n\n### Building\n```bash\nmake open          # Open Provenance.xcworkspace in Xcode\nmake ios           # Update submodules + build iOS\nmake tvos          # Update submodules + build tvOS\nmake update        # Pull + update submodules + install gems\nmake test          # Run tests via fastlane\n```\n\nBuild from Xcode: open `Provenance.xcworkspace` and select a scheme. Start with `Provenance-Lite` (fastest build) before moving to `Provenance-Release` or `Provenance-XL (Release)`.\n\n**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.\n\n### Schemes\n- **Provenance-Lite (AppStore)** — internal test build only: never published to the App Store and has no users. Not release-gated; don't spend effort on Lite-only upgrade or migration paths (it has its own `lite.realm` since 766c3605ac, with no seed from `default.realm`, by design). Keep it compiling because it's a fast build to validate against.\n- **Provenance (AppStore)** — standard release\n- **Provenance-XL (Release)** — includes more RetroArch and native cores\n- Each has iOS and tvOS variants\n\n### CI\nGitHub Actions (`.github/workflows/build.yml`) builds all target variants on push/PR to `develop` and `master`.\n\n## Architecture\n\n### Module Structure\nThe app is split into ~26 `PV*` Swift Package frameworks. Key modules:\n\n- **PVLibrary** — Data models, Realm persistence, game database, CloudKit sync\n- **PVCoreBridge** — Protocol/bridge between app and emulator cores\n- **PVCoreBridgeRetro** — RetroArch-specific core bridge\n- **PVCoreLoader** — Dynamic loading of emulator core packages\n- **PVEmulatorCore** — Base classes for emulator implementations\n- **PVUI** — SwiftUI-based shared UI components\n- **PVSettings** — User preferences\n- **PVCoreAudio / PVAudio** — Audio engine and playback\n- **PVSupport** — Shared utilities\n- **PVLogging** — Logging infrastructure (OSLog-based; use `DLOG`/`ILOG`/`ELOG`/`WLOG`)\n- **PVPrimitives** — Base data types shared across modules\n- **PVCheevos** — RetroAchievements integration\n- **PVHashing** — ROM file hashing for identification\n- **PVLookup** — Game metadata lookup (separate repo, github.com/Provenance-Emu/PVLookup; databases fetched at build time via `Scripts/lookup/databases.lock.json`)\n- **PVJIT** — JIT compilation support for emulator cores\n\n### Emulator Cores (`Cores/`)\nEach core lives in `Cores/<CoreName>/` and typically contains:\n- A git submodule with the upstream emulator source\n- An Xcode project (`PV<Core>.xcodeproj`) and/or `Package.swift`\n- A bridge layer (`PV<Core>Core/`) with `PV<Core>CoreBridge+Controls.mm` (Objective-C++) connecting the emulator to `PVCoreBridge`\n\nCores depend on `PVEmulatorCore`, `PVCoreBridge`, `PVSupport`, `PVObjCUtils`, and `PVLogging` via relative SPM paths.\n\nRetroArch-based cores live in `CoresRetro/RetroArch/` and use `PVCoreBridgeRetro`.\n\n### App Targets\n- **Provenance/** — Main iOS app\n- **ProvenanceTV/** — tvOS-specific app target\n- **Extensions/** — Spotlight indexing, TopShelf (tvOS)\n\n### Persistence\n- **Realm** for local metadata, game library, and CloudKit record IDs\n- **CloudKit** for syncing ROMs, save states, and BIOS files across devices\n- Realm objects are thread-confined: pass Object IDs or freeze objects for cross-thread access\n\n### Key Patterns\n- CloudKit syncers manage records by directory scope (ROMs, Save States, BIOS) with deterministic record IDs\n- Emulator cores are loaded as dynamic packages at runtime via `PVCoreLoader`\n- Controller input mapping is handled per-core in `*Bridge+Controls.mm` files\n\n## Linting & Formatting\n\n**SwiftLint** (`.swiftlint.yml`):\n- Line length: 200 chars\n- Only lints: `Provenance`, `ProvenanceTV`, `PVLibrary`, `PVSupport`, `TopShelf`, `Spotlight`\n- Excludes: `Cores`, `Carthage`, `Scripts`, `fastlane`, `.build`\n- `force_cast` and `force_try` are warnings (not errors)\n\n**SwiftFormat** (`.swiftformat`):\n- 4-space indent, no indent for `case`\n- Excludes: `Carthage`, `Cores`\n\n## Code Style\n\n### Swift Code\n- Prefer `async/await` over Combine where possible\n- Use `@Sendable` and proper actor isolation for concurrency\n- Avoid force unwraps (`!`) in production code; use `guard let` or `if let`\n- Line length limit: 200 characters (`.swiftlint.yml`)\n- 4-space indentation, no indent for `case` in `switch`\n\n### Emulator Core Bridges (`*Bridge+Controls.mm`)\n- All controller types must be handled: Extended, Micro, Keyboard\n- Button mappings must match upstream emulator constants\n- Never modify upstream submodule source in `Cores/<name>/<upstream-dir>/`\n\n## Common Patterns\n\n### Logging\nUse PVLogging functions — never `print` or `NSLog`:\n```swift\nimport PVLogging\nDLOG(\"Debug: \\(value)\")    // Debug builds only\nILOG(\"Info: \\(message)\")   // Info\nWLOG(\"Warning: \\(msg)\")    // Warning\nELOG(\"Error: \\(error)\")    // Error\nVLOG(\"Verbose detail\")     // Debug builds only (verbose)\n```\n\n### Realm Threading\nRealm objects are thread-confined — never pass them across threads:\n```swift\n// Wrong — crashes across threads:\nlet game = realm.objects(PVGame.self).first!\nTask.detached { _ = game.title } // CRASH\n\n// Right — pass primary key and re-fetch on new thread:\nlet gameID = game.id\nTask.detached {\n    let realm = try await Realm()\n    let game = realm.object(ofType: PVGame.self, forPrimaryKey: gameID)\n}\n\n// Or freeze for read-only cross-thread access:\nlet frozenGame = game.freeze()\n```\n\n### Feature Flags\nGate new features behind `PVFeatureFlags` (Tier 0, importable anywhere):\n```swift\nimport PVFeatureFlags\n\n// Check at runtime (PVFeatureFlags is @MainActor):\nif await PVFeatureFlags.shared.isEnabled(.myFeature) { ... }\n\n// In SwiftUI views via environment:\n@Environment(\\.featureFlags) var featureFlags\nif featureFlags.myFeature { ... }\n```\n\n### Actor Isolation\nUI updates must happen on the main actor:\n```swift\n@MainActor\nclass MyViewModel: ObservableObject { ... }\n\n// From an async context:\nTask { @MainActor in\n    self.isLoading = false\n}\n```\n\n### Platform-Specific Code\n```swift\n#if os(tvOS)\n    // tvOS: no touch input; use Focus Engine and game controllers as primary input\n#elseif os(iOS)\n    // iOS: touch gestures + game controllers\n#endif\n```\n\n## Platform Support Matrix\n\n### Primary Targets (always must compile and work)\n- **iOS 17+** — minimum deployment target\n- **tvOS 17+** — minimum deployment target\n\n### Aspirational Targets (compile where possible, especially non-UI code)\n- **macOS 14+** (Sonoma) — Designed for iPad today; native target planned; Catalyst not supported\n- **watchOS 10+** — limited scope; non-UI utility code only\n- **visionOS 1+** — Aspirational — package declarations + #if os(visionOS) guards only; no app target\n\n### Unit Test Platform\n- **Linux** — Tier 0–2 modules should compile on Linux for CI unit tests via `swift test`\n- Avoid Darwin-only APIs in Tier 0–2 without `#if canImport(Darwin)` guards\n\n### Guards\n```swift\n#if os(iOS)\n    // iOS-only (touch, UIKit specifics)\n#elseif os(tvOS)\n    // tvOS-only (Focus Engine, no touch)\n#elseif os(macOS)\n    // Mac-only\n#elseif os(watchOS)\n    // watchOS-only\n#elseif os(visionOS)\n    // visionOS-only\n#endif\n```\n\n## GitHub Project & Issue Workflow\n\n### Project Board\nAll issues and PRs should be tracked on the [Provenance Roadmap](https://github.com/orgs/Provenance-Emu/projects/1/views/1).\n\n### Labels\nUse these labels consistently:\n- `bug` — regressions or broken behavior\n- `enhancement` — new features or improvements\n- `Epic` — parent issue grouping related sub-tasks\n- `agent-work` — issue assigned to Claude/AI agent lane\n- `cursor-work` — issue assigned to Cursor agent lane\n- `ai-reviewing` — PR currently being reviewed by AI\n- `ai-reviewed` — PR review complete\n- `needs-fixes` — review found issues requiring changes\n- `ready-for-review` — AI review passed, awaiting @JoeMatt\n\n### Epics & Sub-tickets\n- Epic issues use the `Epic` label and list sub-tasks as checkboxes in the body\n- Sub-tasks reference their parent epic with \"Part of #EPIC_NUMBER\" in the PR/issue body\n- Close sub-tasks by merging their PR; epic auto-closes when all checkboxes are checked\n\n### Tagging Reviewers\n- Tag **@JoeMatt** for final human review on any significant change\n- Tag **@copilot** in a PR comment to request a Copilot review pass\n- Tag **@claude** in a PR comment to request a Claude fix/review pass\n- Do NOT tag both simultaneously — one at a time\n\n### WhatsNew / Release Notes\nSee \"## WhatsNew & Release Versioning\" section below for rules.\n\n## WhatsNew & Release Versioning\n\n### CRITICAL: Never Guess Version Numbers\n\nThe `whats-new.json` file (`PVUI/Sources/PVSwiftUI/Resources/whats-new.json`) drives\nthe in-app \"What's New\" sheet. Agents MUST NOT add entries with speculative future version\nnumbers. **The current released version is the latest git tag** — check with:\n```bash\ngit tag --sort=-v:refname | head -1\n```\n\n### Rules for Agents\n1. **Only add a `whats-new.json` entry when**: the PR contains a major user-visible feature AND the target version has been explicitly confirmed in the issue/PR\n2. **Never invent version numbers** — if unsure, skip the entry and note it in the PR body with `TODO: add whats-new entry for version X.Y.Z`\n3. **Version format**: semver string matching the Xcode `MARKETING_VERSION` (e.g. `\"3.3.0\"`)\n4. **Max 6 features** per version entry\n5. **Do NOT modify existing entries** for already-shipped versions\n\n### Changelog Fragments\nPrefer adding `.changelog/<PR_NUMBER>.md` fragments over editing `CHANGELOG.md` directly.\nThe consolidation workflow (`consolidate-changelog.yml`) merges fragments on PR merge.\n\n## Important Conventions\n\n- The `develop` branch is the main development branch\n- Emulator core submodules are in `Cores/<name>/<upstream-submodule-dir>` — avoid modifying upstream source directly\n- Each PV* module is a standalone Swift Package with its own `Package.swift`\n- The top-level `Package.swift` is minimal (legacy SPM support for PVLibrary only); the real build system is the Xcode workspace\n- Build variants (Lite/Standard/XL) differ in which cores are included; see `CoresRetro/RetroArch/Scripts/` for core lists per target\n\n## Agent Development Guidelines\n\n### Quick Validation Commands\n```bash\n# Lint changed Swift files\nswiftlint lint --path <file>\n\n# Build a standalone SPM module (Tier 0-2 only)\ncd PV<Module> && swift build\n\n# Test a standalone SPM module\ncd PV<Module> && swift test\n\n# Xcode simulator build (full app, slow)\nxcodebuild build -workspace Provenance.xcworkspace \\\n  -scheme \"Provenance-Lite (AppStore)\" \\\n  -destination \"generic/platform=iOS Simulator\" \\\n  CODE_SIGNING_ALLOWED=NO | xcpretty\n```\n\n### Module Dependency Tiers\nModules are organized by dependency depth. Scope changes to the lowest tier possible.\n\n| Tier | Modules | Can `swift build` standalone? |\n|------|---------|------------------------------|\n| 0 | PVObjCUtils, PVFeatureFlags, PVCheevos | Yes |\n| 1 | PVLogging, PVPlists, PVHashing | Yes |\n| 2 | PVSettings, PVPrimitives | Yes |\n| 3 | PVSupport, PVAudio, PVCoreAudio | Needs Xcode |\n| 4 | PVCoreBridge, PVEmulatorCore, PVShaders | Needs Xcode |\n| 5 | PVCoreBridgeRetro, PVCoreLoader, PVLibrary | Needs Xcode |\n| 6 | PVUI, App targets | Full workspace build |\n\n### Emulator Core Bridge Pattern\nEach core has a `PV<Core>CoreBridge+Controls.mm` file that maps controller input:\n```objc\n- (void)didMoveGamepad:(GCExtendedGamepad *)gamepad {\n    // Map GCController buttons to emulator-specific button constants\n    // Use PVCoreBridge protocol methods to forward input\n}\n```\nWhen modifying bridge files, ensure all controller types are handled (Extended, Micro, Keyboard).\n\n### What NOT to Modify\n- **Submodule source** — `Cores/<name>/<upstream-dir>/` contents are upstream code\n- **Generated files** — `Version.h`, `Version.swift`, files in `cmake/` build dirs\n- **CodeSigning.xcconfig** — contains developer-specific credentials\n- **project.pbxproj** — avoid when possible; prefer SPM Package.swift changes\n\n### What NOT to Flag in Code Review\n- `force_cast` and `force_try` are intentionally warnings, not errors\n- Generated files (`Version.h`, `Version.swift`) — skip review\n- Submodule contents (`Cores/<name>/<upstream>/`) — skip review\n- `project.pbxproj` changes — minimize review noise\n\n### PR Requirements\n- Target the `develop` branch\n- Include test coverage for new logic (where testable)\n- Keep scope focused — one logical change per PR\n- Run `swiftlint` on changed files before submitting\n- Agent PRs should use the `[Agent]` prefix in title\n\n### Branch Naming & Commit Messages\n- Branches: `agent/issue-<N>` for agent work, `feature/<description>` for features\n- Commits: Use conventional commits (`fix:`, `feat:`, `chore:`, `build:`, `refactor:`, `test:`, `docs:`)\n- Keep commit messages concise (< 72 chars for subject line)\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Provenance-Emu/Provenance/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}