Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9cd1a9a
Add guest-wide host-adaptive-off comparison patch
Zection6V Aug 17, 2026
fb32ca3
Allow comparison patch in shared runtime patch stack
Zection6V Aug 17, 2026
485f64f
Build comparison runner with host adaptive output disabled
Zection6V Aug 17, 2026
e8fcc4f
Document host-adaptive-off comparison build
Zection6V Aug 17, 2026
b08e3f9
Mark comparison branch as test-only
Zection6V Aug 17, 2026
aee1746
Stretch guest-wide native frame only at presentation
Zection6V Aug 17, 2026
107a1ff
Clarify native-frame stretch comparison architecture
Zection6V Aug 17, 2026
dd79e26
Mark native-frame stretch comparison build
Zection6V Aug 17, 2026
0bae77e
Expose MPH guest aspect patch as independent runtime option
Zection6V Aug 17, 2026
c0c75c2
Add launcher mod toggle for game aspect patch
Zection6V Aug 17, 2026
da25635
Decouple host and guest widescreen runtime policies
Zection6V Aug 17, 2026
b791877
Expose guest aspect patch in generated launcher Mods
Zection6V Aug 17, 2026
2bf2d6d
Remove hardcoded host-off widescreen experiment
Zection6V Aug 17, 2026
8a9cd4b
Remove obsolete hardcoded comparison marker
Zection6V Aug 17, 2026
e755c1a
Document independent host and guest widescreen toggles
Zection6V Aug 17, 2026
23202b5
Replace hardcoded host-off test notes with A/B matrix
Zection6V Aug 17, 2026
a4b4051
Validate widescreen A/B patch helpers in CI
Zection6V Aug 17, 2026
35a7ce6
Force nearest-neighbor framebuffer presentation
Zection6V Aug 17, 2026
7b94197
Keep framebuffer presentation nearest-only
Zection6V Aug 17, 2026
8f67865
Validate nearest-presentation patch helper
Zection6V Aug 17, 2026
db7e9db
Cover independent aspect mod and nearest presentation in static CI
Zection6V Aug 17, 2026
1786deb
Keep aspect-ratio layer idempotent in runtime stack
Zection6V Aug 17, 2026
5941c1b
Make widescreen launcher mods mutually exclusive
Zection6V Aug 17, 2026
c93d47b
Guard widescreen mod mutual exclusion
Zection6V Aug 17, 2026
50271f4
Document mutually exclusive widescreen modes
Zection6V Aug 17, 2026
c10c3a6
Note verified launcher behavior
Zection6V Aug 17, 2026
de126c5
Merge pull request #7 from Zection6V/agent/test-guest-wide-host-off
Zection6V Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ jobs:
python -m py_compile \
tools/patch_ndsrecomp_mph_runtime.py \
tools/patch_ndsrecomp_mph_adaptive_capability.py \
tools/patch_ndsrecomp_mph_aspect_ratio_mod.py \
tools/patch_ndsrecomp_nearest_presentation.py \
tools/patch_mph_launcher_aspect_mod.py \
tools/patch_ndsrecomp_rom_free_release.py \
tools/patch_recomp_ui_mph_multirom.py \
tools/ci/check_rom_free_release_sources.py \
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/mph-multirom-static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ jobs:
tools/patch_ndsrecomp_mph_runtime.py \
tools/patch_ndsrecomp_mph_runtime_core.py \
tools/patch_ndsrecomp_mph_widescreen.py \
tools/patch_ndsrecomp_mph_adaptive_capability.py \
tools/patch_ndsrecomp_mph_widescreen_reset.py \
tools/patch_ndsrecomp_mph_aspect_ratio_mod.py \
tools/patch_ndsrecomp_nearest_presentation.py \
tools/patch_mph_launcher_aspect_mod.py \
tools/promote_mph_static_coverage.py \
tools/promote_mph_runtime_coverage.py \
tools/probe_mph_wfc.py \
Expand Down Expand Up @@ -199,8 +203,11 @@ jobs:
for file in /tmp/mph-launcher-us10/launcher_main_profile.cpp /tmp/mph-launcher-eu11/launcher_main_profile.cpp; do
grep -q 'game.known_sha1_hex = nullptr;' "$file"
grep -q 'game.num_known_sha1 = 0;' "$file"
grep -A1 'int mod_feature_count' "$file" | grep -q 'return 3;'
grep -A1 'int mod_feature_count' "$file" | grep -q 'return 4;'
grep -q 'copy_text(output->id, "hd-rendering")' "$file"
grep -q 'copy_text(output->id, "game-aspect-ratio-patch")' "$file"
grep -q 'bool aspect_ratio_patch = false;' "$file"
grep -q -- '--mph-aspect-ratio-patch' "$file"
grep -q -- '--firmware-state-path' "$file"
grep -q 'bool adaptive_widescreen = true;' "$file"
grep -q 'std::filesystem::is_regular_file(default_rom, initial_rom_error)' "$file"
Expand Down Expand Up @@ -234,8 +241,19 @@ jobs:
diff -u /tmp/first.sha256 /tmp/second.sha256
grep -q 'nds_title_patches_select_mph_runtime_profile' /tmp/ndsrecomp/runner/src/main.cpp
grep -q 'nds_title_patches_set_mph_adaptive' /tmp/ndsrecomp/runner/src/main.cpp
grep -q -- '--mph-aspect-ratio-patch' /tmp/ndsrecomp/runner/src/main.cpp
grep -q 'slirp_virtual_network_instance' /tmp/ndsrecomp/runner/src/main.cpp
grep -q '0x02111B5Cu, 0x0211D208u, 0x02111380u' /tmp/ndsrecomp/runner/src/mph_widescreen_profiles.generated.h
grep -q 'SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY, "0"' /tmp/ndsrecomp/runner/src/frontend.cpp
test "$(grep -c 'SDL_SetTextureScaleMode' /tmp/ndsrecomp/runner/src/frontend.cpp)" -ge 2
if grep -A3 'SDL_HINT_RENDER_SCALE_QUALITY' /tmp/ndsrecomp/runner/src/frontend.cpp | grep -q '"1"'; then
echo 'linear SDL framebuffer scaling unexpectedly remains enabled' >&2
exit 1
fi
grep -q 'GL_TEXTURE_MIN_FILTER, GL_NEAREST' /tmp/ndsrecomp/runner/src/melonds_compute/ComputeHost.cpp
grep -q 'GL_TEXTURE_MAG_FILTER, GL_NEAREST' /tmp/ndsrecomp/runner/src/melonds_compute/ComputeHost.cpp
grep -q 'GL_TEXTURE_MIN_FILTER, GL_NEAREST' /tmp/ndsrecomp/runner/vendor/melonds/GPU3D_Compute.cpp
grep -q 'GL_TEXTURE_MAG_FILTER, GL_NEAREST' /tmp/ndsrecomp/runner/vendor/melonds/GPU3D_Compute.cpp
! grep -q 'kMphUs10MorphState' /tmp/ndsrecomp/runner/src/frontend.cpp
! grep -q 'kMphUs10AimX' /tmp/ndsrecomp/runner/src/title_patches.cpp

Expand Down
29 changes: 29 additions & 0 deletions docs/WIDESCREEN_AB_TEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Widescreen modes

The launcher exposes two alternative widescreen implementations:

- **Adaptive Widescreen** — the original ndsrecomp host-side 448px renderer / compositor / HUD anchoring path.
- **Game Aspect Ratio Patch** — the MPH guest-side 21:9 projection/culling patch derived from melonPrimeDS and mphCodex.

They are **mutually exclusive**. Turning either widescreen feature ON immediately turns the other one OFF. Both may be OFF for native 4:3 output, but both may not be ON at the same time.

The game-side patch defaults **OFF**, so the original ndsrecomp Adaptive Widescreen implementation remains the default widescreen path.

## Valid combinations

| Adaptive Widescreen | Game Aspect Ratio Patch | Result |
|---|---|---|
| OFF | OFF | Native 4:3 / 256x192 |
| ON | OFF | Original ndsrecomp host widescreen only |
| OFF | ON | Guest projection/culling patch; native 256x192 top image is stretched to 448x192 only at final presentation |
| ON | ON | **Invalid state** — the launcher automatically switches one side OFF |

When only the guest patch is enabled, the DS-native render surface remains 256x192. The final 256-to-448 stretch is required because the game-side patch produces projection geometry for a 21:9 target; displaying the resulting native surface unchanged as 4:3 would make it appear horizontally compressed.

Legacy `mods.ini` files from the comparison build may contain both values as `true`. The loader resolves that state while parsing and the next save writes a valid mutually-exclusive pair. The final process-launch argument construction also refuses to pass both widescreen mechanisms to the runner even if an invalid state is somehow introduced later.

The runner logs the selected guest policy. Guest-side code/data writes remain fail-closed and require an authoritative supported MPH executable checksum; header-only fallback never authorizes the aspect-ratio patch.

## Validation

The separate host-only / guest-only presentation paths and the nearest-only Supersampling presentation fix were visually tested with MPH before mutual exclusion was enabled. The current launcher policy then passed the full ROM-free Windows/Linux build and static regression suite at head `50271f43a4d0be9fe4c02a87c278bec80f4a4a47`.
75 changes: 69 additions & 6 deletions launcher/recomp-ui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,29 @@ set(MPH_PROFILE_LAUNCHER_SOURCE
"${CMAKE_CURRENT_BINARY_DIR}/launcher_main_profile.cpp")
file(WRITE "${MPH_PROFILE_LAUNCHER_SOURCE}" "${MPH_LAUNCHER_SOURCE}")

# Configure-time regression guard for the fresh-release UX. The generated
# launcher must never pass a missing conventional ROM path into recomp-ui as if
# the user had selected it. This deliberately tests the generated TU rather
# than the untransformed upstream-tracking source.
# MPH display policy layer. Keep the upstream-tracking launcher source simple,
# but expose the melonPrimeDS/mphCodex game-side projection/culling patch as a
# separate Mods item in the generated launcher. It defaults OFF; the existing
# Adaptive Widescreen feature remains the original host-side ndsrecomp path.
# The two features are radio-button-like: enabling either disables the other.
execute_process(
COMMAND "${Python3_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/../../tools/patch_mph_launcher_aspect_mod.py"
--source "${MPH_PROFILE_LAUNCHER_SOURCE}"
RESULT_VARIABLE _mph_aspect_mod_result
OUTPUT_VARIABLE _mph_aspect_mod_stdout
ERROR_VARIABLE _mph_aspect_mod_stderr)
if(NOT _mph_aspect_mod_result EQUAL 0)
message(FATAL_ERROR
"Failed to add MPH launcher aspect-ratio mod:\n"
"${_mph_aspect_mod_stdout}${_mph_aspect_mod_stderr}")
endif()
message(STATUS "${_mph_aspect_mod_stdout}")
file(READ "${MPH_PROFILE_LAUNCHER_SOURCE}" MPH_LAUNCHER_SOURCE)

# Configure-time regression guards for the generated launcher. The release must
# start without a fake ROM selection, and the two widescreen mechanisms must
# remain separately addressable while never being enabled together.
string(FIND "${MPH_LAUNCHER_SOURCE}"
"std::filesystem::is_regular_file(default_rom, initial_rom_error)"
_mph_initial_rom_exists_guard)
Expand All @@ -88,6 +107,48 @@ string(FIND "${MPH_LAUNCHER_SOURCE}"
if(_mph_initial_rom_argument_guard EQUAL -1)
message(FATAL_ERROR "generated launcher still passes the unconditional default ROM path")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"mph-game-aspect-ratio-patch"
_mph_aspect_mod_feature_guard)
if(_mph_aspect_mod_feature_guard EQUAL -1)
message(FATAL_ERROR "generated launcher lost the Game Aspect Ratio Patch Mods feature")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"--mph-aspect-ratio-patch"
_mph_aspect_mod_arg_guard)
if(_mph_aspect_mod_arg_guard EQUAL -1)
message(FATAL_ERROR "generated launcher does not pass the guest aspect-ratio policy to nds_runner")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"bool aspect_ratio_patch = false;"
_mph_aspect_mod_default_guard)
if(_mph_aspect_mod_default_guard EQUAL -1)
message(FATAL_ERROR "Game Aspect Ratio Patch must remain opt-in by default")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"MPH_WIDESCREEN_MUTUAL_EXCLUSION"
_mph_widescreen_exclusion_guard)
if(_mph_widescreen_exclusion_guard EQUAL -1)
message(FATAL_ERROR "generated launcher lost widescreen mutual-exclusion logic")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"if (state->adaptive_widescreen) state->aspect_ratio_patch = false;"
_mph_adaptive_disables_guest_guard)
if(_mph_adaptive_disables_guest_guard EQUAL -1)
message(FATAL_ERROR "Adaptive Widescreen no longer disables Game Aspect Ratio Patch")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"if (state->aspect_ratio_patch) state->adaptive_widescreen = false;"
_mph_guest_disables_adaptive_guard)
if(_mph_guest_disables_adaptive_guard EQUAL -1)
message(FATAL_ERROR "Game Aspect Ratio Patch no longer disables Adaptive Widescreen")
endif()
string(FIND "${MPH_LAUNCHER_SOURCE}"
"((adaptive && !mods.aspect_ratio_patch) ? L\"top\" : L\"none\")"
_mph_runner_exclusion_guard)
if(_mph_runner_exclusion_guard EQUAL -1)
message(FATAL_ERROR "runner launch path can pass both widescreen implementations")
endif()

add_executable(mph-recomp-ui "${MPH_PROFILE_LAUNCHER_SOURCE}"
"${NDSRECOMP_ROOT}/recompiler/support/sha1.cpp")
Expand Down Expand Up @@ -128,7 +189,9 @@ endif()
enable_testing()
add_executable(mph-mod-provider-test tests/launcher_mod_provider_test.cpp
"${NDSRECOMP_ROOT}/recompiler/support/sha1.cpp")
# The test #includes launcher_main.cpp whole, so it needs ndsrecomp's sha1.h.
# The baseline provider test still covers the upstream-tracking source. The
# generated launcher itself is compiled above and guarded for the additional
# MPH aspect-ratio feature at configure time.
target_include_directories(mph-mod-provider-test PRIVATE
"${NDSRECOMP_ROOT}/recompiler/support"
"${CMAKE_CURRENT_SOURCE_DIR}"
Expand All @@ -143,4 +206,4 @@ include("${RECOMP_UI_ROOT}/recomp_ui.cmake")
recomp_target_launcher_ui(mph-recomp-ui CONSOLE nds
BOXART "${CMAKE_CURRENT_SOURCE_DIR}/assets/boxart.tga")

add_test(NAME mph_mod_provider_test COMMAND mph-mod-provider-test)
add_test(NAME mph_mod_provider_test COMMAND mph-mod-provider-test)
Loading
Loading