fix(pvp): pause on opponent disconnect/leave mid-run instead of instant win - #504
Open
ChronoFinale wants to merge 1 commit into
Open
fix(pvp): pause on opponent disconnect/leave mid-run instead of instant win#504ChronoFinale wants to merge 1 commit into
ChronoFinale wants to merge 1 commit into
Conversation
Any opponent departure mid-run -- including a transient network drop -- instantly showed the local player the win screen. The MQTT bridge's PLAYER_LEFT handler called the gamemode's on_player_forfeit as soon as G.STAGE == RUN, with no distinction between a deliberate leave and a dropped connection, and it never subscribed to the API's PLAYER_DISCONNECTED/PLAYER_RECONNECTED events at all -- so the existing pause/countdown machinery (action_enemyDisconnected/action_enemyReconnected, MP.enemy_disconnect_countdown) was dead code in the MQTT flow. The API's lobby event stream also cannot reliably distinguish a deliberate leave from a drop: `player_left` fires both for an explicit lobby:leave() and for an ungraceful connection drop whose retained players/<id>/info topic clears, which can arrive before, instead of, or without ever seeing a player_disconnected event first, with no reason field to tell them apart. Given that ambiguity, every mid-run opponent departure now routes into the same pause/grace flow (pvp_api/disconnect_grace.lua's MP.decide_departure_action), and only local grace expiry ends the match -- there is no server anymore to send stopGame on timeout, so the client resolves the forfeit itself, exactly once, through the existing host-authoritative on_player_forfeit path. - pvp_api/disconnect_grace.lua: new pure decision core -- event + state in, ignore/start_grace/cancel_grace out -- plus the single-fire expiry guard. - pvp_api/lobby_bridge.lua: subscribe to PLAYER_DISCONNECTED/PLAYER_RECONNECTED, route all three departure events through the decision core, and add the grace-expiry forfeit shell (MP.resolve_enemy_disconnect_forfeit). - networking/action_handlers.lua: track the departed player_id on the countdown, resolve it locally on expiry instead of waiting on a server stopGame that no longer exists. - tests/test_disconnect_grace.lua: pure-core scenarios, a RED control proving the old routing forfeited instantly on a network drop, and a shell-wiring test against a fake MPAPI lobby.
ChronoFinale
marked this pull request as ready for review
July 23, 2026 23:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An opponent disconnecting or leaving mid-run instantly showed the win screen (any
player_lefttriggered an immediate forfeit). Route departures through the grace/pause path instead; only forfeit after the grace window.Rebased onto current
mqtt— re-expressed against upstream's result-passing gamemode convention + reconnect-tail handler. Test:luajit tests/test_disconnect_grace.lua— 22/22 green.Closes #483