[Agent] feat(ds): enable skin support for melonDS, desmume2015, and ThinLibretroCore DS (Phase 1)#3377
[Agent] feat(ds): enable skin support for melonDS, desmume2015, and ThinLibretroCore DS (Phase 1)#3377github-actions[bot] wants to merge 3 commits intodevelopfrom
Conversation
|
🤖 PR created. AI review starting automatically. |
There was a problem hiding this comment.
Pull request overview
Phase 1 of enabling Nintendo DS dual-screen Delta skin activation across native DS cores (melonDS, DeSmuME2015) and the ThinLibretro DS system path, so the existing DefaultDeltaSkin NDS layout can be used.
Changes:
- Enable
supportsSkins = trueforPVMelonDSCoreandPVDesmume2015Core. - Allow ThinLibretro DS (
com.provenance.ds) to use skins by removing it from the unsupported list and advertisingsupportsDualScreens. - Add a changelog fragment documenting DS dual-screen skin support (Phase 1).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| PVCoreBridgeRetro/Sources/PVLibRetro/PVThinLibretroCore.swift | Enables DS skins for ThinLibretro and marks DS as dual-screen capable. |
| Cores/melonDS/PVMelonDSCore/Core/PVMelonDSCore.swift | Turns on Delta skin support for the native melonDS core. |
| Cores/Desmume2015/PVDesmume2015Core/Core/PVDesmume2015Core.swift | Turns on Delta skin support for the native DeSmuME2015 core. |
| .changelog/3372.md | Adds release note fragment for Phase 1 DS skin enablement. |
| public final class PVMelonDSCore: PVEmulatorCore { | ||
| /// Dual-screen skin layouts are not yet supported; disable until implemented. | ||
| public override var supportsSkins: Bool { false } | ||
| public override var supportsSkins: Bool { true } |
There was a problem hiding this comment.
Enabling skins for a core that already reports supportsDualScreens == true will route viewport/layout through PVEmulatorViewController+DualScreen. That code currently reads swapScreen via KVC (value(forKey:)) on the core bridge, which will raise an Objective‑C NSUndefinedKeyException for melonDS (no swapScreen property found in the melonDS bridge). Please either (a) make the dual-screen controller logic DS-safe (only query swap for cores that actually expose it, or use responds(to:)/a dedicated protocol), or (b) keep skins disabled for this core until the dual-screen path is corrected.
| public override var supportsSkins: Bool { true } | |
| // Skins are disabled for melonDS until the dual-screen controller path is made DS-safe (swapScreen handling). | |
| public override var supportsSkins: Bool { false } |
| open class PVDesmume2015Core: PVEmulatorCore { | ||
| /// Dual-screen skin layouts are not yet supported; disable until implemented. | ||
| public override var supportsSkins: Bool { false } | ||
| public override var supportsSkins: Bool { true } |
There was a problem hiding this comment.
Same issue as melonDS: setting supportsSkins = true while supportsDualScreens = true will drive the UI into the generic dual-screen layout code, which currently queries swapScreen on the bridge via KVC. DeSmuME2015’s bridge does not define swapScreen, so this risks an NSUndefinedKeyException crash when applying the dual-screen viewport. Please gate the swap-screen KVC access to cores that implement it (or replace with a protocol) before enabling DS skins here.
| public override var supportsSkins: Bool { true } | |
| // Skins are disabled for now to avoid KVC access to a missing `swapScreen` selector | |
| // in the generic dual-screen-with-skins layout code. | |
| public override var supportsSkins: Bool { false } |
| /// DS (NDS) cores output a combined 256×384 framebuffer (top screen + bottom screen). | ||
| /// The DefaultDeltaSkin NDS layout splits this into two independently positioned viewports. | ||
| public override var supportsDualScreens: Bool { | ||
| systemIdentifier == "com.provenance.ds" |
There was a problem hiding this comment.
Setting supportsDualScreens for ThinLibretro DS cores will push DS games into the same dual-screen viewport code path used for 3DS. That path currently assumes the core bridge supports a swapScreen KVC property and will crash with NSUndefinedKeyException for bridges that don’t implement it (likely for libretro DS bridges). Please make the dual-screen controller logic DS-safe (only query swap screen for emuThree/Citra-style bridges, or use responds(to:)/a dedicated protocol) before advertising dual-screen support here.
| systemIdentifier == "com.provenance.ds" | |
| guard systemIdentifier == "com.provenance.ds" else { return false } | |
| // Only advertise dual-screen support when the underlying bridge actually exposes | |
| // a `swapScreen` KVC property (getter + setter). The dual-screen controller/viewport | |
| // path used for 3DS assumes this property exists and will crash with | |
| // `NSUndefinedKeyException` if it does not, so we gate DS dual-screens on this check. | |
| if let objcBridge = bridge as? NSObject { | |
| let hasGetter = objcBridge.responds(to: NSSelectorFromString("swapScreen")) | |
| let hasSetter = objcBridge.responds(to: NSSelectorFromString("setSwapScreen:")) | |
| return hasGetter && hasSetter | |
| } | |
| return false |
| /// DS (NDS) cores output a combined 256×384 framebuffer (top screen + bottom screen). | ||
| /// The DefaultDeltaSkin NDS layout splits this into two independently positioned viewports. | ||
| public override var supportsDualScreens: Bool { | ||
| systemIdentifier == "com.provenance.ds" |
There was a problem hiding this comment.
Avoid hard-coding the DS system identifier string here; PVSystems.SystemIdentifier.DS.rawValue is already defined and reduces the risk of typos / identifier drift.
| systemIdentifier == "com.provenance.ds" | |
| systemIdentifier == PVSystems.SystemIdentifier.DS.rawValue |
e5bf327 to
2b6bac7
Compare
|
@claude please review copilot review issues, validate their correctness, make appropriate changes, then self re-review and comment the summary |
✅ Copilot Review AddressedCompleted
What was fixed
What was dismissed
Completed: 2026-03-25 |
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot Review — Issues AddressedReviewed all 4 Copilot comments. 3 were valid and corrected; 1 was addressed by the root fix. Issues fixedIssue 1 & 2 — melonDS / DeSmuME2015: KVC crash on The guard bridge.responds(to: NSSelectorFromString("swapScreen")) else { return false }This makes Issue 3 — ThinLibretroCore Replaced Issue 4 (ThinLibretroCore Files changed
|
|
🤖 Merge conflict detected — Claude has been dispatched to resolve the conflicts automatically. If conflicts cannot be resolved cleanly, the workflow will post a comment explaining what needs manual attention. |
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
d439845 to
8e44c03
Compare
✅ Rebase Conflict ResolvedRebased Conflict Summary
Rebase completed cleanly. Branch pushed with |
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8e44c03 to
981633d
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
981633d to
1a5351e
Compare
28efb6a to
1f2058f
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1f2058f to
527423c
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
527423c to
f981077
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f981077 to
452d6ef
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
452d6ef to
3aad1a2
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3aad1a2 to
cd7b102
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cd7b102 to
72bff54
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
72bff54 to
9e82117
Compare
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9e82117 to
09453cf
Compare
|
🤖 Merge conflict detected — Claude has been dispatched to resolve the conflicts automatically. If conflicts cannot be resolved cleanly, the workflow will post a comment explaining what needs manual attention. |
✅ Rebase Conflicts ResolvedRebased Conflict in
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
PVCoreBridgeRetro/Sources/PVLibRetro/PVThinLibretroCore.swift:76
- Removing DS from
skinUnsupportedSystemsenables Delta skins for thin libretro DS cores, but the current app render path still uses a single GPU view and cannot display the default NDS skin’s two independently positioned screens (especially landscape side‑by‑side). This likely results in an incorrect/squished viewport when skins are enabled. Consider keeping DS in the unsupported set until #3373 lands, or gating DS to a single-screen default viewport until dual-screen sub-rect rendering is implemented.
/// NOTE: "com.provenance.ds" is intentionally NOT listed here — DS skins
/// use the DefaultDeltaSkin NDS dual-screen layout (portrait + landscape).
private static let skinUnsupportedSystems: Set<String> = [
"com.provenance.3ds",
"com.provenance.dos",
"com.provenance.mame",
"com.provenance.arcade",
"com.provenance.palmos",
| ### Added | ||
| - **DS Dual-Screen Skin Support (Phase 1)** — Enable Delta skin support for melonDS, desmume2015, and PVThinLibretroCore DS system; NDS dual-screen portrait/landscape layouts were already present in DefaultDeltaSkin and are now active. |
There was a problem hiding this comment.
This changelog entry implies the NDS dual-screen portrait/landscape layouts are now “active”. With the current render pipeline still treating the DS framebuffer as a single view (no per-screen sub-rectangle rendering yet; see #3373), enabling skins may not actually present the intended side-by-side landscape layout. Consider rewording this to avoid promising correct dual-screen rendering until the Metal sub-rect work is merged.
✅ AI Review Approved — Ready for Human ReviewGitHub Copilot has approved this PR. The following actions have been taken:
This PR will merge automatically once all required checks pass and @JoeMatt approves. 🤖 Processed by Claude Code agent |
|
@claude please audit all Copilot review comments on this PR. For each comment:
After addressing the review comments, do a self-review pass:
Then re-request Copilot review. |
|
✅ Fixes pushed — addressed Copilot review on PR #3377 Review Audit SummaryFixed ✅
Dismissed (already handled or nit-only) 🔕KVC
Low-confidence suppressed comment (DS in |
…troCore DS - Set supportsSkins = true on PVMelonDSCore and PVDesmume2015Core - Remove com.provenance.ds from PVThinLibretroCore skinUnsupportedSystems - Add supportsDualScreens = true for DS system in PVThinLibretroCore - DefaultDeltaSkin NDS dual-screen portrait/landscape layouts already present Part of #2540 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The dual-screen viewport code accessed `swapScreen` via KVC without checking whether the bridge implements it, causing an NSUndefinedKeyException for melonDS, DeSmuME2015 and ThinLibretro DS bridges. - Use responds(to:) before value(forKey: "swapScreen") in isScreenSwapped - Replace hardcoded "com.provenance.ds" with SystemIdentifier.DS.rawValue Fixes Copilot review issues on PR #3377. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…angelog - Replace raw string literals in skinUnsupportedSystems with SystemIdentifier enum .rawValue values (per CLAUDE.md mandate). "com.provenance.arcade" kept as literal (no enum case defined). - Reword .changelog/3377.md to avoid implying full dual-screen rendering works; references #3373 for the pending Metal sub-rect work. - KVC swapScreen crash concerns dismissed: isScreenSwapped in PVEmulatorViewController+DualScreen already guards with responds(to:) before value(forKey:) — no crash path exists. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Phase 1 of DS dual-screen skin support: enable skin loading for all three DS core paths so the existing
DefaultDeltaSkinNDS dual-screen layout is activated.supportsSkins = trueonPVMelonDSCore(was blocked by a "not yet supported" guard)supportsSkins = trueonPVDesmume2015Core(same)"com.provenance.ds"fromPVThinLibretroCore.skinUnsupportedSystemsso RetroArch DS cores (melonDS-libretro, desmume) also load skinssupportsDualScreens = truetoPVThinLibretroCorefor the DS system identifierThe
DefaultDeltaSkin.ndsScreens()method — which returns twoDeltaSkinScreenobjects splitting the combined 256×384 framebuffer into portrait-stacked or landscape-side-by-side layouts — was already fully implemented. This PR simply unblocks it from being used.What this enables
With skins enabled, DS games will load the default NDS skin showing:
inputFramemetadata for future Metal sub-rectangle rendering ([Feature] DS Dual-Screen: Metal sub-rectangle framebuffer rendering #3373)What's still needed (follow-up issues)
y=0..191for top,y=192..383for bottom screenTest plan
Part of
Part of #2540 — DS Dual-Screen Skin System