Skip to content

Practice mode: replay past matches#387

Open
stephenkirk wants to merge 15 commits intomainfrom
stephenkirk/review-practice-mode
Open

Practice mode: replay past matches#387
stephenkirk wants to merge 15 commits intomainfrom
stephenkirk/review-practice-mode

Conversation

@stephenkirk
Copy link
Copy Markdown
Member

Summary

Practice mode lets you replay past matches. Pick a recorded game, choose which side to play, and the other side's scoring plays back hand-by-hand while you play live. Same seed, same blinds, same ruleset — different you.

Replay sources are Lovely .log files or exported .json. Drop them in replays/ and they show up in the picker.

What's new

A full replay practice flow: browse recorded matches in a two-column picker (match list + detail panel with joker sprites), flip which side you play as, then run the game with MP rulesets and blind progression. Between rounds, open the collection and click to spawn cards. Toggle unlimited slots or edition cycling as needed.

Internals:

  • lib/practice_mode.luaMP.SP state, MP.is_practice_mode()
  • lib/ghost_replay.lua — replay engine: per-ante hand playback, PvP resolution, advance animation
  • lib/log_parser.lua — parses .log files into structured replay data; also accepts .json
  • ui/.../ghost_replay_picker.lua — two-column picker with match details and joker sprites
  • lovely/practice.toml — key/click patches for card spawning (3/click) and edition cycling (Q)
  • MP.is_mp_or_ghost() — replaces MP.LOBBY.code checks across blind, HUD, round, and end-round paths; suppresses network actions behind not MP.GHOST.is_active() guards
  • MP.get_active_gamemode() — centralized resolution (lobby → replay → ruleset). Fixes a prior bug where the replay's gamemode bled into MP.LOBBY.config

State isolation

Replay/practice state must never leak into real MP. action_start_game() (both networking stacks), setup_run_singleplayer(), start_vanilla_sp(), and start_lobby() all clear practice and replay state. Network actions are gated on not MP.GHOST.is_active(). Gamemode reads from replay state, not lobby config.

Known issues

  • Advance animation doesn't pace correctly: when the player outscores the replay mid-round, remaining hands should stagger with 0.6s delays. They fire but blast through instantly. Root cause unknown.
  • should_use_the_order() returns true unconditionally for practice mode. Should check the active ruleset. Intentional deferral.

Test plan

  • Drop .log/.json into replays/, verify they appear in the picker
  • Select a match, verify detail panel (ante breakdown, joker sprites)
  • Flip perspective, verify scores and side swap
  • Play a full replayed match — win and lose paths
  • Spawn cards from collection (click/3), toggle unlimited slots, cycle editions (Q)
  • Exit to real MP lobby or vanilla SP — verify clean state

Use position 2 for better alignment and give Jimbo a more
in-character, playful hint message.
Match recording (snapshot_ante, init, finalize) was dead code —
written but never read. Replays will come from Lovely log files
instead. Removes the table, all methods, and all call sites in
action_handlers and game_state.
Utility predicate doesn't belong in a UI monkey-patch file.
Moved to lib/ where MP.GHOST lives, which loads before ui/.
More descriptive local name for the module table, consistent with
how it's used by callers via pcall/load_mp_file.
The .json path is mainly for verifying the Lua log parser against the
Python tool output. Extracted to load_json_replay() with a comment
explaining its purpose. The .log branch is now checked first as the
primary code path.
Moved is_ruleset_supported, format_score, and build_label from the
picker UI file to MP.GHOST in the lib layer where they belong.
File no longer contains match recording — it's purely ghost replay
load/playback logic now.
…eplay.lua

Move inline ghost replay branching into focused helpers:
- resolve_pvp_hands_exhausted, resolve_pvp_mid_hand, resolve_round_fail
- get_blind_name_ui, current_target, _start_advance_sequence
All replay sources now emit per-hand data. The fallback path that
set a static enemy score from ante-level aggregates was vestigial.

Removes get_enemy_score(), the else-branch in init_playback(), and
the current_target() wrapper — its sole callsite now uses
current_target_big() directly.
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