#257: Add Load Game screen to main menu with sorted save list, loading overlay during quickload, pending_load_path cross-scene flow
#561: Move debug_overlay.gd from scripts/ui/ to ui/ for consistency with all other UI components
Changes
New: client/ui/loading_screen.gd + .tscn — full-screen overlay during save/load round-trip
New: Load Game button + panel in main menu with saves sorted by date
New: GameState.pending_load_path for cross-scene load dispatch
Moved: debug_overlay.gd from scripts/ui/ to ui/
Updated: main.tscn, main_menu.tscn, main.gd, game_state.gd, ui-strings.yaml, test file
Test plan
F5 quicksave writes file to user://saves/<game-id>/quicksave.sav
F6 quickload shows loading overlay, restores state
Main menu Load Game lists saves sorted by date (newest first)
Empty saves list shows 'No saves found.' message
Loading overlay dismisses on save_result (success or failure)
debug_overlay.gd works at new path, test passes
make ci-client passes (pre-existing failures excluded)
## Summary
- **#257**: Add Load Game screen to main menu with sorted save list, loading overlay during quickload, pending_load_path cross-scene flow
- **#561**: Move debug_overlay.gd from scripts/ui/ to ui/ for consistency with all other UI components
## Changes
- New: `client/ui/loading_screen.gd` + `.tscn` — full-screen overlay during save/load round-trip
- New: Load Game button + panel in main menu with saves sorted by date
- New: `GameState.pending_load_path` for cross-scene load dispatch
- Moved: `debug_overlay.gd` from `scripts/ui/` to `ui/`
- Updated: `main.tscn`, `main_menu.tscn`, `main.gd`, `game_state.gd`, `ui-strings.yaml`, test file
## Test plan
- [ ] F5 quicksave writes file to `user://saves/<game-id>/quicksave.sav`
- [ ] F6 quickload shows loading overlay, restores state
- [ ] Main menu Load Game lists saves sorted by date (newest first)
- [ ] Empty saves list shows 'No saves found.' message
- [ ] Loading overlay dismisses on save_result (success or failure)
- [ ] debug_overlay.gd works at new path, test passes
- [ ] `make ci-client` passes (pre-existing failures excluded)
#257: Add Load Game screen to main menu with sorted save list, loading
overlay during quickload round-trip, and pending_load_path cross-scene
flow. F5/F6 quicksave/quickload were already wired.
#561: Move debug_overlay.gd from scripts/ui/ to ui/ for consistency
with all other UI components. Update scene and test references.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Save/load flow has a critical race condition in live mode.
#
File
Severity
Issue
1
client/scripts/main.gd:51-64
critical
LOAD_GAME sent in _ready() before SimBridge is connected — send_input() silently drops the command in live mode, loading screen stays up permanently
2
client/ui/main_menu.gd:97-102
warning
Empty newest_save silently cancels load — no error shown, game starts in default state
3
client/ui/main_menu.gd:73-76
warning
queue_free() race in _build_saves_list() — rapid reopen can double the save list for one frame
4
client/scripts/main.gd:178-181
suggestion
F6 quickload show_loading() before send_input — stuck screen if send fails
Tyre (Architecture): APPROVE
Architecture is sound — correctly respects D-010/D-020 client-server split. pending_load_path is a pragmatic cross-scene handoff in existing GameState autoload pattern. File placement correct.
#
File
Severity
Issue
1
client/ui/main_menu.gd
warning
pending_load_path not cleared in _on_new_game() / _on_continue() — stale path could trigger wrong load
2
client/ui/loading_screen.gd
suggestion
No failure state affordance — hide_loading(success: bool) would future-proof the API
3
client/tests/
suggestion
No test_save_load_flow_sprint21.gd — pattern established, logical gap
Verdict: CHANGES REQUESTED
The critical issue is the LOAD_GAME race in main.gd._ready() — must defer dispatch until SimBridge connection is established. Also fix the stale pending_load_path defensive clears and the empty newest_save guard.
## Review: client -> main (PR #73, type: code)
### Hoshe (Code Quality): REQUEST_CHANGES
Save/load flow has a critical race condition in live mode.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | `client/scripts/main.gd:51-64` | **critical** | `LOAD_GAME` sent in `_ready()` before SimBridge is connected — `send_input()` silently drops the command in live mode, loading screen stays up permanently |
| 2 | `client/ui/main_menu.gd:97-102` | warning | Empty `newest_save` silently cancels load — no error shown, game starts in default state |
| 3 | `client/ui/main_menu.gd:73-76` | warning | `queue_free()` race in `_build_saves_list()` — rapid reopen can double the save list for one frame |
| 4 | `client/scripts/main.gd:178-181` | suggestion | F6 quickload `show_loading()` before `send_input` — stuck screen if send fails |
### Tyre (Architecture): APPROVE
Architecture is sound — correctly respects D-010/D-020 client-server split. `pending_load_path` is a pragmatic cross-scene handoff in existing GameState autoload pattern. File placement correct.
| # | File | Severity | Issue |
|---|------|----------|-------|
| 1 | `client/ui/main_menu.gd` | warning | `pending_load_path` not cleared in `_on_new_game()` / `_on_continue()` — stale path could trigger wrong load |
| 2 | `client/ui/loading_screen.gd` | suggestion | No failure state affordance — `hide_loading(success: bool)` would future-proof the API |
| 3 | `client/tests/` | suggestion | No `test_save_load_flow_sprint21.gd` — pattern established, logical gap |
### Verdict: CHANGES REQUESTED
The critical issue is the `LOAD_GAME` race in `main.gd._ready()` — must defer dispatch until SimBridge connection is established. Also fix the stale `pending_load_path` defensive clears and the empty `newest_save` guard.
- Defer LOAD_GAME dispatch until SimBridge reaches CONNECTED (critical)
- Guard _build_saves_list() against queue_free() race on rapid reopen
- Disable save entries with empty newest_save, guard in _on_save_selected
- Send before show_loading on F6 quickload, skip overlay on send failure
- Clear pending_load_path in _on_new_game()/_on_continue() (stale path)
- Add hide_loading(success: bool) API for future failure-state UI
- Add test_save_load_flow_sprint21.gd covering LoadingScreen + GameState
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CRITICAL race condition — fixed via deferred dispatch (_on_sim_connected_for_load signal handler + _dispatch_pending_load()). Signal disconnection is safe.
Empty newest_save — saves without files shown as disabled buttons + early return with push_error.
queue_free() race — _list_built flag prevents rebuild on rapid reopen.
Stale pending_load_path — cleared at top of _on_new_game() and _on_continue().
_dispatch_pending_load fails silently to player when send_input errors — no notification shown
No automated test for the deferred dispatch signal wiring itself (hard to test in GdUnit4)
Tyre (Architecture): APPROVE
Deferred dispatch pattern is architecturally sound and consistent with existing connection_state_changed usage. D-010 boundaries respected — pending_load_path stays client-side.
Verdict: APPROVED
All blockers resolved. Clean to merge.
## Re-review: client -> main (PR #73, round 2)
### Hoshe (Code Quality): APPROVE
All 6 issues from round 1 verified fixed:
1. **CRITICAL race condition** — fixed via deferred dispatch (`_on_sim_connected_for_load` signal handler + `_dispatch_pending_load()`). Signal disconnection is safe.
2. **Empty `newest_save`** — saves without files shown as disabled buttons + early return with `push_error`.
3. **`queue_free()` race** — `_list_built` flag prevents rebuild on rapid reopen.
4. **Stale `pending_load_path`** — cleared at top of `_on_new_game()` and `_on_continue()`.
5. **`hide_loading` param** — `hide_loading(success: bool = true)` added.
6. **Test file** — 8 tests covering LoadingScreen API and `pending_load_path` field.
Remaining low-priority observations (non-blocking):
- `_dispatch_pending_load` fails silently to player when `send_input` errors — no notification shown
- No automated test for the deferred dispatch signal wiring itself (hard to test in GdUnit4)
### Tyre (Architecture): APPROVE
Deferred dispatch pattern is architecturally sound and consistent with existing `connection_state_changed` usage. D-010 boundaries respected — `pending_load_path` stays client-side.
### Verdict: APPROVED
All blockers resolved. Clean to merge.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Changes
client/ui/loading_screen.gd+.tscn— full-screen overlay during save/load round-tripGameState.pending_load_pathfor cross-scene load dispatchdebug_overlay.gdfromscripts/ui/toui/main.tscn,main_menu.tscn,main.gd,game_state.gd,ui-strings.yaml, test fileTest plan
user://saves/<game-id>/quicksave.savmake ci-clientpasses (pre-existing failures excluded)Review: client -> main (PR #73, type: code)
Hoshe (Code Quality): REQUEST_CHANGES
Save/load flow has a critical race condition in live mode.
client/scripts/main.gd:51-64LOAD_GAMEsent in_ready()before SimBridge is connected —send_input()silently drops the command in live mode, loading screen stays up permanentlyclient/ui/main_menu.gd:97-102newest_savesilently cancels load — no error shown, game starts in default stateclient/ui/main_menu.gd:73-76queue_free()race in_build_saves_list()— rapid reopen can double the save list for one frameclient/scripts/main.gd:178-181show_loading()beforesend_input— stuck screen if send failsTyre (Architecture): APPROVE
Architecture is sound — correctly respects D-010/D-020 client-server split.
pending_load_pathis a pragmatic cross-scene handoff in existing GameState autoload pattern. File placement correct.client/ui/main_menu.gdpending_load_pathnot cleared in_on_new_game()/_on_continue()— stale path could trigger wrong loadclient/ui/loading_screen.gdhide_loading(success: bool)would future-proof the APIclient/tests/test_save_load_flow_sprint21.gd— pattern established, logical gapVerdict: CHANGES REQUESTED
The critical issue is the
LOAD_GAMErace inmain.gd._ready()— must defer dispatch until SimBridge connection is established. Also fix the stalepending_load_pathdefensive clears and the emptynewest_saveguard.All review comments addressed in
64bf4ec:Hoshe (Code Quality):
Tyre (Architecture):
Re-review: client -> main (PR #73, round 2)
Hoshe (Code Quality): APPROVE
All 6 issues from round 1 verified fixed:
_on_sim_connected_for_loadsignal handler +_dispatch_pending_load()). Signal disconnection is safe.newest_save— saves without files shown as disabled buttons + early return withpush_error.queue_free()race —_list_builtflag prevents rebuild on rapid reopen.pending_load_path— cleared at top of_on_new_game()and_on_continue().hide_loadingparam —hide_loading(success: bool = true)added.pending_load_pathfield.Remaining low-priority observations (non-blocking):
_dispatch_pending_loadfails silently to player whensend_inputerrors — no notification shownTyre (Architecture): APPROVE
Deferred dispatch pattern is architecturally sound and consistent with existing
connection_state_changedusage. D-010 boundaries respected —pending_load_pathstays client-side.Verdict: APPROVED
All blockers resolved. Clean to merge.
Pull request closed