Skip to content

Develop - #699

Merged
Zection6V merged 13 commits into
mainfrom
develop
Aug 30, 2026
Merged

Develop#699
Zection6V merged 13 commits into
mainfrom
develop

Conversation

@Zection6V

Copy link
Copy Markdown
Collaborator

No description provided.

…sform and weapon

Value 0 of each method key is the behaviour you get without any new method, but
it had no checkbox: it was whatever "nothing ticked" happened to mean. It is now
a first-class option, so each domain reads as exactly-one-of-N.

The grouping in the dialog moves from pairwise exclusion to a real group:
ticking one box clears the others in its domain, and clearing the last ticked
box puts it back rather than leaving the group empty. They stay checkboxes to
match the rest of the dialog while behaving as a radio group.

No config schema change. The stored integer already encoded 0 as the default,
so an existing config keeps its method and simply shows Standard ticked when it
holds 0.

Labels are localized for all 76 languages, derived from each language's own
existing "new method" wording; ten zoom strings inflect around the numeral and
carry an explicit form. The three new descriptions cover the directly-cased
languages and fall back to English elsewhere.
… in play

All six native paths -- weapon New / New 2, transform New / New 2, zoom New 2 /
New 3 -- now refuse to fire while the local player's HP is 0. A native call
reaches past whatever the game does with a player who is not in play, so the
request is dropped rather than deferred: it is not queued, and one already
queued when the player leaves play is cleared instead of firing on respawn.

The gate sits at both the host queue site and the ARM9 dispatch, because the
queue-to-dispatch window is several frames wide and the player can leave play
inside it. A native weapon request refuses outright rather than rerouting to the
legacy path, so the selected method never silently swaps for one window.

The predicate is IsLocalPlayerAlive() rather than a "dead" one: HP 0 also covers
not-yet-spawned at match start, so "dead" would have named only half of what it
tests. An unresolved HP pointer counts as alive, so a native path is never
disabled just because the pointer cache has not been rebuilt.

Standard Method is untouched -- that is the game reacting to the emulator's own
simulated input, so the game's own handling already applies.

Also restores two sections of input-methods.md, Zoom and Lifecycle and
interactions, that commit e2bbabf dropped when it replaced the code cave
section by range.
…ayer is in play

The latch that gates every battle-runtime patch and every match-scoped ARM9 hook
fired on the first mode==0x0E && flow==0 frame. That pair says the match is
live, not that the local player exists in it, so everything hung off the latch
landed during the spawn window.

It is now two-stage, and the stages need not fall on the same frame:

- BIT_BATTLE_RUNTIME_SEEN is sticky and records that mode/flow reported a live
  match at least once since join. The pair can stop reporting one again while
  the player is still waiting to spawn, so once seen it stays seen.
- BIT_BATTLE_RUNTIME_MODE, the latch proper, completes on the first frame after
  that where the local player's HP is not 0.

This moves the apply point of the eight PatchSite_BattleRuntime registry entries
(OsdColor, LowHpWarning, FpsCameraLock, ShowHeadshotOnline,
ShowEnemyHpMeterOnline, DisableDoubleDamageMultiplier, NoPickingUpSpecificItems,
TouchScreenAimOnly), the match ARM9 hook install, and the guest trampoline
authoring that hangs off it. The match-end poll still lives behind the latch, so
a match that never reaches stage 2 is cleaned up by the leave-in-game path,
which is also the path that never applied anything in the first place.

An unresolved HP pointer counts as in play, so the latch cannot stall on a cold
pointer cache. The new flag is cleared wherever BIT_BATTLE_RUNTIME_MODE is, and
the three m_flags.packed = 0 resets cover it already.
…hook

The battle-runtime latch is a match boundary; respawn is a player boundary, and
the two are not the same. Spawn (JP1_0 0201212C) restores HP early but keeps
initialising camera, model, animation, gun and HUD state well past that point,
and the same player runtime update (0200EEAC) then falls through to the player
input update (02026374) and its hook sites. "HP is not 0, the player matches the
local one, the latch is on, and we reached the hook" is therefore all satisfied
inside the very update that spawned the player, and a native call made there
lands on half-initialised state.

Every native dispatcher -- DirectInvocation (transform / weapon / zoom Method
2/3), TransformGate, WeaponSwitch and NativeZoomToggle -- now clears its pending
request and returns false on that one hook, letting the site's original
instruction run untouched. Dropped, not deferred: replaying a pressed edge from
before the respawn is the behaviour being prevented.

The boundary needs no host latch. Spawn stores the hunter's configured
invulnerability into player+0xE1 and the same update decrements it exactly once
before reaching the input code, so on that hook and only there
player+0xE1 == (uint8_t)([player+0x404] + 0xE2) - 1. The next update reads
configured-2, so this costs one input frame instead of the whole invulnerability
window, and a native transform or zoom is available again immediately after.

Structure offsets are the same on all seven ROMs, so the shared helper needs no
per-version address table; it lives in MelonPrimeGameInput.cpp because all four
hook fragments are unity-included there. Weapon Method 1 keeps its existing
producer-side refusal for the whole invulnerability window unchanged.

Evidence: mphCodex Direct-Invocation-Spawn-Freeze-Investigation-JP1_0.md.
…h the shared barrier

Both weapon methods refused the whole player+0xE1 != 0 window on the producer
side and rerouted that request to the legacy touch path. With the shared
first-post-spawn barrier now in every native dispatcher, that older guard is
both wider than the ROM's own boundary -- the unsafe moment is the one input
hook inside the update that spawned the player, not the entire invulnerability
countdown -- and a silent method swap for its duration.

Removed from both branches, leaving the dispatcher barrier as the single owner
of the spawn boundary for every native method. Nothing else changes: the
in-play (HP != 0) refusal and the native-only "report, don't hide" behaviour on
an unserviceable site both stay.

This deliberately diverges from the mphCodex investigation, which recommends
keeping the wider weapon-only guard as a safety margin rather than weakening it
on the strength of that investigation alone. Restoring a full-window refusal for
the weapon path is the documented fallback if a switch during spawn
invulnerability turns out to still be unsafe.
…nerability window

Method 2 still froze at match start with the first-post-spawn barrier in place,
while Method 1 did not. The barrier is present and correct on the shared
dispatcher, and the trampoline disassembles as intended on all seven ROMs, so
the one-hook boundary is simply not the whole unsafe region for this path.

The investigation documents exactly this fallback (section 10, option B): refuse
while player+0xE1 != 0 rather than only on the hook where it reads
configured - 1. Applied to the transform command only, so weapon and zoom keep
the exact barrier and the producer-side full-window weapon guard removed in the
previous commit is not quietly reinstated.

The asymmetry with Method 1 is consistent with where each calls the routine:
Method 1 redirects into the game's own transform call site, whose surrounding
state the game already treats as valid for a form switch, while Method 2 calls
it from the player input hook.

Clearing the transform request mid-dispatch has to re-check IsValid(), because
the command selection below treats "not transform, not weapon" as zoom.

Unverified: this is a mitigation chosen from the investigation's documented
options, not a root cause that has been observed. If it holds, the remaining
question is whether the window can be narrowed again.
…le invulnerability window"

This reverts commit 3cd23ee.
…tandard transform fires

"Use the Whole Touch Screen for Aiming" neutralises the bottom-screen HUD
hit-tests, and the Standard transform works by tapping the touch-screen
Transform button, so enabling that option silently stops Standard transforms
from firing. Standard weapon change is unaffected because it drives the game's
weapon-change state directly rather than through a HUD button, which is why only
one of the two appears to break.

The new sub-option, shown indented under its parent, restores the patch for the
few frames of that tap and puts it straight back. This is the same bracketing
the legacy weapon path already does around NoDoubleTapJump, and it goes through
the owning module so TouchScreenAimOnly stays the single writer of its patch
state.

Off by default, and only enabled in the dialog while the parent option is
checked, since it does nothing otherwise. The binding row is appended so every
historical binding index stays stable; only the tail load range grows.

Labels are localized for the directly-cased languages and fall back to English
elsewhere. The .ui grid rows below the insertion point shift by one.
…ard path

Restores the full-window fallback the weapon new method used to have, and
extends it to every native method rather than leaving it weapon-only.

While the local player is inside the spawn invulnerability countdown
(player+0xE1 != 0), a native method no longer makes a native call: weapon goes
through the legacy touch route, and transform and zoom fall through to their
Standard branches. The native request is cleared rather than left pending, so
nothing replays when the window ends. Zoom keeps the shared pressed-edge latch
in sync across the boundary, the same way the alt-form branch already does, so
leaving the window mid-hold cannot fire a stale toggle.

The dispatcher's first-post-spawn barrier stays. The two cover different cases
and are complementary: the producer check cannot see a request queued before the
boundary that only reaches the hook after it, and the barrier only sees requests
that actually reach a hook.

The visible trade is that during the window the selected method is briefly not
the one in use -- but the input still does something rather than being
swallowed, which is what the weapon path did before and why it was worth
restoring.
@Zection6V
Zection6V merged commit a0dd361 into main Aug 30, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant