Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
0c5d415
Add bounded X3 Zero playable-character experiment
mstan Sep 29, 2026
9734aea
Show a Zero badge on the player health bar
mstan Sep 29, 2026
da73a88
Use the original X3 Zero health badge and palette
mstan Sep 29, 2026
bce2fd7
Keep Zero replacement tied to submitted sprites during blinking
mstan Sep 29, 2026
4e92e02
Mirror original Zero animations and replace pause-menu and life artwork
mstan Sep 29, 2026
dacce26
Use original Zero muzzle offsets with native X1 weapons
mstan Sep 29, 2026
bdca72b
Keep Zero through menu fades and draw a front-facing life head
mstan Sep 29, 2026
dee8405
Fit Zero's weapon effects and replace the life icon
mstan Sep 29, 2026
9cef5fa
Match Zero charge tiers and original X3 firing recovery
mstan Sep 29, 2026
f8843c6
Polish Zero life icon and title cursor; stop looping charge audio
mstan Sep 29, 2026
8a50ab0
Add grounded Select exchange with original X and Zero teleport effects
mstan Sep 29, 2026
edb212e
Document character HP, X2/X3 weapons and later co-op requirements
mstan Sep 29, 2026
e26055a
Keep separate X and Zero HP with active-only healing
mstan Sep 29, 2026
c501105
Extract original X2 and X3 boss weapon sprite assets from local ROMs
mstan Sep 29, 2026
12c0e1e
Add X2 and X3 pause weapon pages with original menu art and persisten…
mstan Sep 29, 2026
2d1b8f7
Add extended projectile combat foundation with source-based Spinning …
mstan Sep 29, 2026
9778b1a
Integrate extended weapon energy with native HUD and pickups
mstan Sep 29, 2026
2516b0d
docs: record source ROM release requirements and port findings
mstan Sep 29, 2026
686dd61
feat: port X3 Acid Burst attacks and X1 tile collision
mstan Sep 29, 2026
bdbb9b7
docs: record Ray Splasher source behavior and fractional cost
mstan Sep 29, 2026
1f32e76
docs: consolidate complete Zero implementation and source handoff
mstan Sep 29, 2026
62c4aa2
feat: preserve fractional source-weapon energy and legacy saves
mstan Sep 29, 2026
58b4848
feat: port Ray Splasher burst and charged radial turret
mstan Sep 29, 2026
3a47df8
feat: port Sonic Slicer ricochets and charged five-blade attack
mstan Sep 29, 2026
b24c8e3
feat: configure Zero and weapon mods with shared source ROMs
mstan Sep 29, 2026
e1dd07b
fix: enclose imported weapon symbols in the native gameplay HUD footer
mstan Sep 29, 2026
8cc75a6
Merge Zero 0.0.1 release into the separate X2/X3 weapons branch
mstan Sep 29, 2026
1dcfc16
Fix extended weapon HUD badges and shoulder cycling
mstan Sep 29, 2026
26deef0
Merge branch 'fix/zero-ready-red' into feat/x2-x3-weapons
mstan Sep 29, 2026
524c1a9
Merge remote-tracking branch 'origin/main' into feat/x2-x3-weapons
mstan Sep 29, 2026
e3d6af0
Backport X2 Spin Wheel normal and charged attacks
mstan Sep 29, 2026
912d25e
Correct X3 Parasitic Bomb and Frost Shield asset identities
mstan Sep 29, 2026
4fc392f
Use original X2 and X3 gameplay HUD sprites
mstan Sep 29, 2026
22ab0a1
Keep Ray Splasher firing pose active for its full burst
mstan Sep 29, 2026
a6a04db
Scale imported weapon damage by source buster ratios
mstan Sep 29, 2026
88fb2fc
Port Frost Shield ice phases and rideable water platform
mstan Sep 29, 2026
5c3943c
Port Bubble Splash and correct full-charge release detection
mstan Sep 29, 2026
1258985
Port Magnet Mine with original explosions and projectile absorption
mstan Sep 29, 2026
f4b8cda
Port Speed Burner and preserve charged releases while moving
mstan Sep 29, 2026
5d43007
Restore buster firing after cycling imported weapon sets
mstan Sep 29, 2026
7b93079
Use original static graphics for normal Magnet Mine
mstan Sep 29, 2026
4df0809
Port Tornado Fang drills and original rotating charged assets
mstan Sep 29, 2026
ce8d694
Document the integrated Tornado Fang checkpoint
mstan Sep 29, 2026
925a5f1
Keep Spin Wheel suspended through repeated enemy contacts
mstan Sep 29, 2026
c3faaaf
Port Silk Shot materials, gathering and source damage
mstan Sep 29, 2026
a9ddc6f
Port X3 Triad Thunder formation and charged ground strike
mstan Sep 29, 2026
69619d1
Port X2 Strike Chain attacks, wall pull and pickup retrieval
mstan Sep 29, 2026
e7a76e3
Document thirteen integrated weapon ports and remaining work
mstan Sep 29, 2026
7779c10
Clarify X weapon references and intentional Zero adaptations
mstan Sep 29, 2026
d984a2a
Preserve Parasitic Bomb and Gravity Well source investigations
mstan Sep 29, 2026
285037c
Restore X3 Zero charge colors and stored saber glow; clear stale char…
mstan Sep 29, 2026
6cb26d2
Port Crystal Hunter crystallization, shatter and charged time distortion
mstan Sep 29, 2026
d7efa5f
Port Gravity Well pull, charged lift and original casting art
mstan Sep 29, 2026
ef48194
Port Parasitic Bomb capture and charged targeting attacks
mstan Sep 29, 2026
3719998
Restore source drill debris and underwater dash bubbles
mstan Sep 29, 2026
a65d15c
Keep crystallized enemies visible inside Crystal Hunter shells
mstan Sep 29, 2026
a970e17
Adapt Zero pilot presentation to native Ride Armor pose offsets
mstan Sep 29, 2026
a34d31f
Give Speed Burner and Triad Thunder native X1 elemental reactions
mstan Sep 29, 2026
3350f99
Match charged Spinning Blade contact recoil and source timing
mstan Sep 29, 2026
2ce7925
Record native canister occlusion and accepted couch co-op requirements
mstan Sep 30, 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
22 changes: 16 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.20)
project(MegaManXSNESRecomp VERSION 1.6.6 LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/mmx_weapon_sources.h"
COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_x_weapon_tables.py"
"${CMAKE_CURRENT_SOURCE_DIR}/tools/data/x_weapon_assets.json"
"${CMAKE_CURRENT_BINARY_DIR}/mmx_weapon_sources.h"
DEPENDS tools/generate_x_weapon_tables.py tools/data/x_weapon_assets.json VERBATIM)

set(SNESRECOMP_BUILD_VERSION "${PROJECT_VERSION}" CACHE STRING "Release stamp for crash reports")
set(SNESRECOMP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/snesrecomp" CACHE PATH "snesrecomp checkout")
Expand Down Expand Up @@ -36,7 +41,9 @@ function(add_mmx_variant TARGET GEN_DIR CFG_DIR)
src/mmx_spc_player.c src/mods/mmx_diagnostics_plugin.c
src/mods/mmx_widescreen_plugin.c src/mods/mmx_msu1_plugin.c
src/mods/mmx_password_plugin.c src/mmx_password_save.cpp
src/mods/mmx_zero_plugin.c src/mmx_zero.c src/mmx_source_assets.cpp)
src/mods/mmx_zero_plugin.c src/mmx_zero.c src/mmx_weapons.c src/mmx_weapon_combat.c
src/mmx_source_assets.cpp "${CMAKE_CURRENT_BINARY_DIR}/mmx_weapon_sources.h")
target_compile_features(${TARGET} PRIVATE cxx_std_17)
target_include_directories(${TARGET} PRIVATE
${CFG_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR} ${SNESRECOMP_RUNNER_INCLUDE_DIRS})
Expand Down Expand Up @@ -71,7 +78,8 @@ function(add_mmx_variant TARGET GEN_DIR CFG_DIR)
${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_overrides.py
${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_renderer_capture.py
${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_password_hooks.py
${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_zero_hooks.py)
${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_zero_hooks.py
${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_weapon_hooks.py)
add_custom_target(${TARGET}_widescreen_overrides
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_overrides.py
--gen-dir ${GEN_DIR}
Expand All @@ -81,6 +89,8 @@ function(add_mmx_variant TARGET GEN_DIR CFG_DIR)
--gen-dir ${GEN_DIR}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_zero_hooks.py
--gen-dir ${GEN_DIR}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/apply_weapon_hooks.py
--gen-dir ${GEN_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Applying Mega Man X generated-code mod hooks" VERBATIM)
add_dependencies(${TARGET} ${TARGET}_widescreen_overrides)
Expand All @@ -102,8 +112,8 @@ add_mmx_variant(RockmanXSNESRecomp "${CMAKE_CURRENT_SOURCE_DIR}/variants/jp/gen"
include(CTest)
if(BUILD_TESTING)
add_executable(mmx_source_assets_test tests/mmx_source_assets_test.cpp src/mmx_source_assets.cpp
${SNESRECOMP_ROOT}/runner/src/sha256.c)
target_include_directories(mmx_source_assets_test PRIVATE src ${SNESRECOMP_ROOT}/runner/src)
${SNESRECOMP_ROOT}/runner/src/sha256.c "${CMAKE_CURRENT_BINARY_DIR}/mmx_weapon_sources.h")
target_include_directories(mmx_source_assets_test PRIVATE src ${CMAKE_CURRENT_BINARY_DIR} ${SNESRECOMP_ROOT}/runner/src)
target_compile_features(mmx_source_assets_test PRIVATE cxx_std_17)
add_executable(mmx_zero_test tests/mmx_zero_test.c src/mmx_zero.c)
target_include_directories(mmx_zero_test PRIVATE src)
Expand All @@ -118,10 +128,10 @@ if(BUILD_TESTING)
add_executable(mmx_wide_policy_test tests/mmx_wide_policy_test.c src/mmx_wide_policy.c)
target_include_directories(mmx_wide_policy_test PRIVATE src)
add_test(NAME mmx_wide_policy COMMAND mmx_wide_policy_test)
add_executable(mmx_renderer_test tests/mmx_renderer_test.c src/mmx_renderer.c src/mmx_render_assets.c src/mmx_display.c src/mmx_wide_policy.c src/mmx_zero.c)
add_executable(mmx_renderer_test tests/mmx_renderer_test.c src/mmx_renderer.c src/mmx_render_assets.c src/mmx_display.c src/mmx_wide_policy.c src/mmx_zero.c src/mmx_weapons.c src/mmx_weapon_combat.c)
target_include_directories(mmx_renderer_test PRIVATE src ${SNESRECOMP_ROOT}/runner/src)
add_test(NAME mmx_custom_renderer COMMAND mmx_renderer_test)
add_executable(mmx_render_capture tools/mmx_render_capture.c src/mmx_renderer.c src/mmx_render_assets.c src/mmx_display.c src/mmx_wide_policy.c src/mmx_zero.c)
add_executable(mmx_render_capture tools/mmx_render_capture.c src/mmx_renderer.c src/mmx_render_assets.c src/mmx_display.c src/mmx_wide_policy.c src/mmx_zero.c src/mmx_weapons.c src/mmx_weapon_combat.c)
target_include_directories(mmx_render_capture PRIVATE src ${SNESRECOMP_ROOT}/runner/src)
foreach(mmx_test mmx_display_test mmx_wide_policy_test mmx_renderer_test mmx_password_save_test mmx_zero_test)
if(MSVC)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ buster/saber combo, and grounded Select exchange with X. Both characters have
separate current health; pickups heal only the active character. Enable it in
**Mods** and select your **Mega Man X3 USA ROM**. Assets are prepared locally
automatically. See [setup and release notes](docs/zero-0.0.1.md) and the
[technical handoff](docs/zero-port.md). X2/X3 boss weapons are a separate follow-up.
[technical handoff](docs/zero-port.md). This branch contains the [X2/X3 weapon follow-up](docs/x-weapons-port.md),
with thirteen weapons implemented and three still using the development fallback.

<p align="center">
<img src="docs/screenshots/widescreen-ocean.png" width="32%" alt="Mega Man X experimental widescreen rendering in an ocean scene">
Expand Down
151 changes: 105 additions & 46 deletions docs/mod-source-roms.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,105 @@
# Public mods: user-supplied source ROMs

The user supplies the supported X1 ROM and an original USA X3 ROM for Zero.
The later X2/X3 weapon expansion requires those games' respective ROMs. This
contract covers all imported sprites, palettes, animation records, collision
data, badges and future audio. No extracted content belongs in a public package.

## Zero 0.0.1 setup

The launcher asks for a **ROM**, never an extracted cache. Its X3 file resource
uses shared key `megaman-x.source.x3`, reserved also for the weapon follow-up.
The provider validates normalized SHA-256, accepts 512-byte copier headers,
persists the path locally, and rejects unsupported ROMs before activation.

Supported X3 USA normalized SHA-256:
`65b03268afac296330e8ff8d60dd0825879e13ed658b37713c034a3bd074f1d7`.

`src/mmx_source_assets.cpp` repeats validation and extracts Zero assets natively
from the selected ROM, atomically publishing `cache/mmx-source/x3-zero-v6.bin`
beside the executable. Failed validation leaves the previous cache untouched.
Activation always requires the selected ROM; it never falls back to developer
assets. Runtime extraction requires no Python or additional download.

The native output is byte-for-byte equivalent to `tools/extract_zero.py`.
The cache contains the original 117 body poses, 21 saber body poses, 14 saber
blade poses, palettes, four HUD tiles, animation records, muzzle offsets and
saber bounds. [Zero's source notebook](zero-port.md) records the ROM addresses.
Code stores addresses and integration logic; imported bytes stay in the cache.

## Release staging

`tools/make_release.ps1` stages the executable, launcher assets, default config,
README, Zero release notes and checked-in mod catalog. It does not copy the
runtime's writable `mods` directory, key bindings or private caches. Catalog
staging rejects untracked files, `state.toml`, ROMs, binary asset caches, saves
and compositor captures. Runtime DLL dependencies are audited from PE imports.
ROMs, extracted cache files and private fixtures are used only for local tests.

## Follow-up weapons contract

Both weapon packs live on the separate `feat/x2-x3-weapons` branch. X3 weapons
reuse `megaman-x.source.x3`; changing it in either feature updates the other.
X2 uses `megaman-x.source.x2` and normalized USA SHA-256
`f3246755f608a1e1dc9c848b61da3b824c7853b29b3be40df6fc7f2793a887ed`.
Both remain subject to the same private extraction and release audit rules.
Co-op is a later mutually exclusive mod; see the [roadmap](zero-weapons-coop-roadmap.md).
# Public mod: user-supplied source ROMs

Owner requirement, recorded 2026-09-29. Applies to the Zero/Select mod, the
X2/X3 weapon expansion, and the later mutually exclusive co-op mod. Scope:
[roadmap](zero-weapons-coop-roadmap.md). Technical findings:
[source notebook](x-weapons-source-notes.md).
Zero implementation/provenance: [Zero port handoff](zero-port.md).

## Distribution contract

The user supplies their own X1, X2 and X3 ROMs for the complete mod. A public
download must not contain ROMs or extracted game assets. This covers **all**
imported content, including Zero's body animations, saber, teleport effects,
HUD badge, menu/title poses, weapon projectiles, icons, palettes, animation
records, and any future imported audio or effects. These are not exceptions
because they are small or embedded in another file.

| Enabled content | Required user-supplied source |
| --- | --- |
| Base X1 game and its native assets | Supported X1 ROM |
| X3 Zero, character exchange and X3-derived visuals | Supported X3 ROM, in addition to X1 |
| X2 weapons alone | Supported X2 ROM, in addition to X1 |
| X3 weapons alone | Supported X3 ROM, in addition to X1 |
| Complete X2/X3 boss-weapon expansion | Supported X2 and X3 ROMs, in addition to X1 |
| Later X/Zero co-op with the completed weapon set | The same X1/X2/X3 sources |

Extract needed data locally from the supplied ROMs. Ship the mod's code,
extractors, address descriptors and documentation. Keep generated caches on
the user's machine; do not bundle a developer's caches or download them as a
substitute for supplying the source ROMs. No ROM-finding/downloading service
is part of setup.

## Launcher setup and automatic extraction

The launcher exposes three independent options: **X3 Zero Experiment**,
**X2 Weapons (experimental)** and **X3 Weapons (experimental)**. Users select
original USA `.sfc`/`.smc` ROMs, never extracted `.bin` files. X2/X3 weapon packs
also work with X alone when Zero is disabled. Pause L/R skips disabled packs.

Zero and X3 weapons both bind `shared_key = "megaman-x.source.x3"`; choosing,
replacing or clearing either picker changes the same canonical X3 path and
immediately appears on the other. X2 uses `megaman-x.source.x2`. The shared
runtime persists these values as `[[shared_resource]]` entries in mod state.
An enabled feature with a missing or incorrect ROM cannot launch. Original
ROM SHA-256 is checked after stripping an optional 512-byte copier header:

- X2: `f3246755f608a1e1dc9c848b61da3b824c7853b29b3be40df6fc7f2793a887ed`
- X3: `65b03268afac296330e8ff8d60dd0825879e13ed658b37713c034a3bd074f1d7`

`src/mmx_source_assets.cpp` performs native extraction at activation. There is
no Python/runtime tool installation or user extraction step. It validates the
ROM again and atomically writes private caches in `cache/mmx-source/` beside
the executable: `x3-zero-v7.bin`, `x2-weapons-v5.bin`, `x3-weapons-v5.bin`.
Each launch regenerates from the selected ROM, so stale/developer caches are
never an implicit fallback. Native extraction matches the reference tools
byte for byte. Copier headers produce identical output; wrong ROMs are rejected.

The weapon descriptor compiles into address-only C++ tables during the normal
developer build. Zero uses the same verified source addresses as the reference
extractor. Separate eight-entry weapon caches use `MMXWEAP5`; the loader also
accepts the combined sixteen-entry development cache in that format. Version 4
added each source game's dedicated 16x16 gameplay HUD footer, separate from its
pause-menu icon. Version 5 adds a palette per sprite group and up to three
groups, allowing original shared explosion art with its own source palette.
Compressed static graphics are decoded from the selected ROM using address-only
descriptors. Old weapon caches are regenerated from the selected ROMs.
Source pack selection and this artwork change do not alter gameplay saves.

Shared framework support originated in `795fc99` and is integrated at
`8566fdb` (framework PR #131), the same tested pin used by Zero 0.0.1
(central issue `beads-8wg.2.90`); game integration is tracked in `beads-8wg.1.35`.
The framework provider test covers both sharing directions, independent keys,
persistence, clearing, copier headers and incorrect-ROM launch rejection.
ROM-backed game checks cover independent packs without Zero, pause page
skipping, native-width composition, firing, exact save/replay and loading both
packs without discarding the first pack's art.
The desktop fresh-boot check activated Zero and both weapon packs using only
the selected source paths and generated all three private caches. No save state
was loaded. `tests/check_source_extraction.py` reproduces byte parity, header
normalization and wrong-ROM rejection without overwriting a valid cache.

## Developer reference tools

- `tools/extract_zero.py` produces the local `zero-x3.bin` cache from X3.
- `tools/extract_x_weapons.py` produces the local `x-weapons.bin` cache from X2
and X3 using `tools/data/x_weapon_assets.json`. It checks normalized source
hashes and accepts a 512-byte copier header.
- These remain reference/debugger tools. Launcher activation requires source
ROMs and generates its own caches; it does not use those developer files.
- Addresses, extraction logic and behavioral findings belong in source
control; ROMs, decoded graphics, asset caches, private save states and
research captures do not belong in public release packages.

## Release boundary and remaining weapon work

Zero 0.0.1 has shipped separately in PR #52 with the native ROM picker and
private extraction. Its package audit stages only the tracked mod catalog,
rejects private state/cache/ROM files, validates DLL closure, and passed a fresh
cold boot from a ZIP install. The weapons branch retains that packaging path.

The X2/X3 weapons remain development content: four combat implementations and
twelve fallbacks. Finish their behavior/fidelity and audit every added resource
before publishing a weapon release. Future sound imports must also come from
the user's ROMs. Recheck the final weapon distributable from a clean install,
including both independent packs and their shared X3 selection with Zero.
Loading
Loading