Close every top modal through one Esc and right-click path
- Add Help, Settings, Pause and review to the top-modal close chain so right-click and Esc behave consistently. - Restore the venue menu after menu Settings and prevent Help from opening Pause behind itself. - Reopen pause after in-shift Settings apply and recapture the mouse/unfreeze state on close. - Extend the session smoke for menu Settings, right-click Help and guard unfreeze.
This commit is contained in:
1 parent
8f65751975
commit
26e6dd6413
8 files changed
+111
-6
No files matched your search
@@ -14,6 +14,9 @@ Semantic Versioning.
|
||||
- Keyboard and gamepad focus rings now wrap through every modal, with explicit
|
||||
default focus and A/B accept/cancel bindings; hidden Continue and disabled
|
||||
timeline controls are skipped.
|
||||
- Esc and right-click now close the top panel consistently, restore the venue
|
||||
menu after menu Settings, and recapture the mouse without leaving the guard
|
||||
frozen. Help no longer opens Pause behind itself.
|
||||
- NPC reason answers now use role-specific dialogue contexts, so staff, guests,
|
||||
delivery workers, performers and other archetypes answer instead of silently
|
||||
deflecting the Reason verb.
|
||||
|
||||
@@ -61,6 +61,12 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
gallery is 13 good / 0 bad, the screen-design judge is 13/13 and a clean
|
||||
portrait measured a 0.732 vision gap against the photoreal concept. No
|
||||
release build, version bump or tag was created at this gate.
|
||||
- **Modal close (2026-11-02):** `GameManager._close_top_modal()` is the single
|
||||
Esc/right-click path for radial, inspection, wand, radio, timeline, Help,
|
||||
Settings, Pause and review. Settings remembers whether it came from the menu and
|
||||
restores it on apply/cancel; `_refresh_freeze()` recaptures the mouse and
|
||||
`_show_start_menu()` keeps it visible. Do not add a new modal without adding it
|
||||
to this close path.
|
||||
- **UI focus (2026-11-02):** `src/game/ui/ui_focus.gd` links visible/enabled
|
||||
controls into a wrapping ring; each modal sets default focus on open. Start menu
|
||||
uses its own focus-target list so Continue, venue rows and footer actions share
|
||||
|
||||
@@ -85,8 +85,11 @@ screen-design judge before any model/texture changes. No gameplay rules change.
|
||||
default focus, confirm/cancel behavior and no focus traps. (Focus rings,
|
||||
gamepad A/B UI bindings, default focus and per-modal cancel behavior are
|
||||
recorded in `docs/ui-copy.md`.)
|
||||
- [ ] Verify every modal closes with Esc/right-click where documented, recaptures
|
||||
the mouse, and does not leave the guard frozen or the menu visible.
|
||||
- [x] Verify every modal closes with Esc/right-click where documented, recaptures
|
||||
the mouse, and does not leave the guard frozen or the menu visible. (Single
|
||||
top-modal close path, menu/settings return handling and pointer recapture are
|
||||
recorded in `docs/ui-copy.md`; session smoke covers menu Settings, Help
|
||||
right-click and pause Settings.)
|
||||
- [ ] Test buttons at minimum/maximum HUD font scale and with 1.5–2× longer
|
||||
strings; fix clipping, overflow and overlapping layouts.
|
||||
- [ ] Test destructive/irreversible actions (campaign reset, tutorial replay,
|
||||
|
||||
@@ -51,6 +51,7 @@ Implemented so far:
|
||||
- Joke Shop / Sunset Strip map (map optimization): the second venue is now a stylized reconstruction of The Comedy Store block at 8433 Sunset Blvd, with a 27 m black hall, signature-covered sloping canopy, circular **THE JOKE SHOP** blade sign, red velvet curtain bays, a west-end entrance, cream and white low-rise neighbours, a dry hillside with stucco houses and palms behind the roofline, and a low retail/billboard frontage across Sunset. The same geometry serves day and late lighting through the phase system. Real-site references and limits are in [`joke-shop-map.md`](joke-shop-map.md); the map measures **22,680 triangles across 454 meshes** in the budget test.
|
||||
- Venue geometry (v0.7.0, Joke Shop frontage superseded by the map-optimization pass): the TV Studio builds a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes.
|
||||
- Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report.
|
||||
- Modal-close pass (0.9.3 work): one top-modal close path handles Esc/right-click for every overlay, Settings opened from the menu restores the menu, Help no longer opens Pause behind it, and pointer/guard freeze state is recaptured after each close. The session smoke covers these paths.
|
||||
- Keyboard/gamepad focus pass (0.9.3 work): `UiFocus` rings wrap through visible/enabled controls, hidden Continue and disabled timeline actions are skipped, each modal has a default focus, and A/B are bound to `ui_accept`/`ui_cancel` for pad-only navigation. Recorded in `docs/ui-copy.md`.
|
||||
- Button-state pass (0.9.3 work): a shared `UiStyles` helper now gives every action button explicit normal, hover, pressed, focus and disabled styles; the timeline disables Previous/Next at its boundaries, while hidden continue states and the keyboard-only tutorial skip remain intentional. Recorded in `docs/ui-copy.md`.
|
||||
- Dialogue pass (0.9.3 work): NPC reason answers now use the authored `reason` context, with an in-character answer for every shared archetype; Hollywood and Studio gained door-staff lines; radio cancellation, tutorial Ask terminology, help, start hints and campaign reset copy were clarified. Audit recorded in `docs/ui-copy.md`.
|
||||
|
||||
@@ -44,3 +44,4 @@
|
||||
42. Button states remain readable on hover/focus/pressed; settings Apply/Cancel and radio normal-send/emergency-hold behave as labelled.
|
||||
43. The venue menu offers Hollywood Boulevard — Day and — Night; either starts the same map, and Night starts in the late phase with a full shift clock.
|
||||
44. The Joke Shop opens on the Sunset Strip club: the black hall, signature canopy, circular **THE JOKE SHOP** sign, red curtains, west entrance, palms, dry hillside with houses, neighbouring frontage and the opposite side of Sunset all read from the door camera. Day lighting and late lighting both look correct; the guard can walk the sidewalk without leaving the nav bounds.
|
||||
45. Esc and right-click close only the top panel (Help, Settings, Pause, Review, Radio, timeline, inspection/wand, action wheel); Settings opened from the venue menu returns to the menu, Help no longer opens Pause behind it, and closing a modal recaptures the mouse without leaving the guard frozen.
|
||||
+25
-1
@@ -1,6 +1,6 @@
|
||||
# UI copy and button inventory
|
||||
|
||||
Status: dialogue, button-state and keyboard/gamepad focus passes complete; layout/end-to-end audit in progress for 0.9.3.
|
||||
Status: dialogue, button-state, focus and modal-close passes complete; layout/end-to-end audit in progress for 0.9.3.
|
||||
|
||||
## Screen inventory
|
||||
|
||||
@@ -128,6 +128,30 @@ events.
|
||||
| Radio | Send action. | B/Esc cancels; form fields, action and Cancel are linked; A confirms the focused action. |
|
||||
| Tutorial | None; F1 is the documented skip shortcut. | Keyboard-only by design, no focus trap because the card is mouse-transparent and non-modal. |
|
||||
|
||||
## Modal close and pointer recapture
|
||||
|
||||
`GameManager._close_top_modal()` is now the single close path for the top
|
||||
interactive layer. It handles the radial, inspection, wand, radio, timeline,
|
||||
Help, Settings, Pause and review before Esc/right-click falls back to target
|
||||
clearing or pause. `_refresh_freeze()` recaptures the mouse when the shift
|
||||
resumes; `_show_start_menu()` keeps the pointer visible at the menu.
|
||||
|
||||
| Surface | Close routes | Result |
|
||||
| --- | --- | --- |
|
||||
| Pause | Resume, Esc, right-click | Closes via one path; guard unfreezes and mouse recaptures. |
|
||||
| Settings | Apply settings, Cancel, Esc, right-click | From the venue menu, both apply and cancel restore the menu. From a shift, cancel returns to the game with the guard unfrozen; apply returns to pause. |
|
||||
| Help | Close, Esc, right-click | Closes only Help; it no longer opens Pause behind the overlay. Returns to whatever was underneath. |
|
||||
| Shift review | Return to menu, Esc, right-click | Returns to the venue menu with the pointer visible. |
|
||||
| Timeline | Close, Esc, right-click | Returns to the review and keeps the pointer visible. |
|
||||
| Radio | Cancel, Esc, right-click | Closes the form, clears the action hint and recaptures the mouse. |
|
||||
| Inspection / wand | Esc, right-click | Closes the panel, unfreezes the guard and recaptures the mouse. |
|
||||
| Action wheel | Esc, right-click | Cancels without a log entry and recaptures the mouse. |
|
||||
| Start menu | Esc quits the application | Root menu; right-click is intentionally inert. |
|
||||
| Tutorial | F1 skip | Non-modal card; never blocks the pointer or movement. |
|
||||
|
||||
The session smoke now covers menu → Settings → Cancel → menu, Help closing on
|
||||
right-click, and Settings cancel from pause leaving the guard unfrozen.
|
||||
|
||||
## Remaining audit work
|
||||
|
||||
- Player-facing labels are now routed through `content/strings.en.json`; numeric
|
||||
|
||||
@@ -99,6 +99,9 @@ var shift_review_panel: ShiftReview = null
|
||||
var start_menu: StartMenu = null
|
||||
var pause_overlay: PauseOverlay = null
|
||||
var settings_panel: SettingsPanel = null
|
||||
## Settings opened from the venue menu must return there on Cancel/Apply;
|
||||
## settings opened from pause must not leave a menu visible or the guard frozen.
|
||||
var _settings_from_start_menu := false
|
||||
var event_timeline: EventTimeline = null
|
||||
var tutorial_overlay: TutorialOverlay = null
|
||||
var help_overlay: HelpOverlay = null
|
||||
@@ -352,8 +355,15 @@ func _phase_start_s(phase: int) -> float:
|
||||
|
||||
func _show_start_menu() -> void:
|
||||
_shift_running = false
|
||||
_settings_from_start_menu = false
|
||||
if pause_overlay:
|
||||
pause_overlay.close()
|
||||
if settings_panel:
|
||||
settings_panel.close()
|
||||
if help_overlay:
|
||||
help_overlay.close_panel()
|
||||
if event_timeline:
|
||||
event_timeline.close()
|
||||
if shift_review_panel:
|
||||
shift_review_panel.hide()
|
||||
if guard:
|
||||
@@ -411,11 +421,21 @@ func _on_settings_applied(settings: Dictionary) -> void:
|
||||
Save.save_campaign(campaign.summary())
|
||||
if coordinator:
|
||||
coordinator.scheduler.spawn_rate_multiplier = float(_difficulty()["spawn"])
|
||||
if start_menu and start_menu.visible:
|
||||
start_menu.open(campaign.summary() if campaign else {})
|
||||
elif _shift_running:
|
||||
# A settings panel opened from the venue menu is restored by
|
||||
# `_on_settings_closed`; an in-shift panel returns to pause. Close it before
|
||||
# opening pause so the new top-modal pass cannot cancel it twice.
|
||||
if _shift_running:
|
||||
if settings_panel:
|
||||
settings_panel.close()
|
||||
_open_pause()
|
||||
|
||||
func _on_settings_closed() -> void:
|
||||
if _settings_from_start_menu:
|
||||
_settings_from_start_menu = false
|
||||
_show_start_menu()
|
||||
else:
|
||||
_refresh_freeze()
|
||||
|
||||
## Clear the tutorial completion/skip flags and restart the Hollywood shift so
|
||||
## the next player can replay the first-shift lesson from Settings.
|
||||
func _on_reset_campaign() -> void:
|
||||
@@ -423,6 +443,7 @@ func _on_reset_campaign() -> void:
|
||||
Save.reset_campaign()
|
||||
if campaign:
|
||||
campaign.setup({})
|
||||
_settings_from_start_menu = false
|
||||
if settings_panel:
|
||||
settings_panel.close()
|
||||
if pause_overlay:
|
||||
@@ -434,6 +455,7 @@ func _on_replay_tutorial() -> void:
|
||||
player_settings["tutorial_skipped"] = false
|
||||
if Save:
|
||||
Save.save_settings(player_settings)
|
||||
_settings_from_start_menu = false
|
||||
if settings_panel:
|
||||
settings_panel.close()
|
||||
if pause_overlay:
|
||||
@@ -445,6 +467,7 @@ func _open_help() -> void:
|
||||
help_overlay.open(selected_scenario.policy_summary if selected_scenario else "")
|
||||
|
||||
func _open_settings() -> void:
|
||||
_settings_from_start_menu = start_menu != null and start_menu.visible
|
||||
if start_menu:
|
||||
start_menu.close()
|
||||
if pause_overlay:
|
||||
@@ -517,6 +540,7 @@ func _build_ui() -> void:
|
||||
settings_panel.applied.connect(_on_settings_applied)
|
||||
settings_panel.replay_tutorial_requested.connect(_on_replay_tutorial)
|
||||
settings_panel.reset_campaign_requested.connect(_on_reset_campaign)
|
||||
settings_panel.closed.connect(_on_settings_closed)
|
||||
|
||||
event_timeline = EventTimeline.new()
|
||||
add_child(event_timeline)
|
||||
@@ -1367,6 +1391,18 @@ func _close_top_modal() -> bool:
|
||||
if event_timeline and event_timeline.visible:
|
||||
event_timeline.close()
|
||||
return true
|
||||
if help_overlay and help_overlay.visible:
|
||||
help_overlay.close_panel()
|
||||
return true
|
||||
if settings_panel and settings_panel.visible:
|
||||
settings_panel._on_cancel()
|
||||
return true
|
||||
if pause_overlay and pause_overlay.visible:
|
||||
_close_pause()
|
||||
return true
|
||||
if shift_review_panel and shift_review_panel.visible:
|
||||
_on_review_restart()
|
||||
return true
|
||||
return false
|
||||
|
||||
func _open_radial() -> void:
|
||||
|
||||
@@ -22,6 +22,33 @@ func _initialize() -> void:
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
_frames += 1
|
||||
if _frames == 5:
|
||||
_manager = _main.get_node_or_null("GameManager")
|
||||
if _manager == null or _manager.start_menu == null or not _manager.start_menu.visible:
|
||||
printerr("start menu not visible before settings check")
|
||||
quit(1)
|
||||
return true
|
||||
_manager._open_settings()
|
||||
if _frames == 7:
|
||||
if _manager.settings_panel == null or not _manager.settings_panel.visible:
|
||||
printerr("settings did not open from the venue menu")
|
||||
quit(1)
|
||||
return true
|
||||
_manager.settings_panel._on_cancel()
|
||||
if _frames == 8:
|
||||
if not _manager.start_menu.visible:
|
||||
printerr("cancelling menu settings did not return to the venue menu")
|
||||
quit(1)
|
||||
return true
|
||||
print("boot: menu settings cancel returns to menu")
|
||||
_manager._open_help()
|
||||
_manager._handle_cancel(true)
|
||||
if _frames == 9:
|
||||
if _manager.help_overlay.visible or not _manager.start_menu.visible:
|
||||
printerr("right-click did not close Help and keep the venue menu")
|
||||
quit(1)
|
||||
return true
|
||||
print("boot: help closes on right-click")
|
||||
if _frames == 10:
|
||||
_manager = _main.get_node_or_null("GameManager")
|
||||
if _manager.start_menu == null or not _manager.start_menu.visible:
|
||||
@@ -65,6 +92,10 @@ func _process(_delta: float) -> bool:
|
||||
return true
|
||||
print("boot: settings visible")
|
||||
_manager.settings_panel._on_cancel()
|
||||
if _manager.guard.input_locked:
|
||||
printerr("guard stayed frozen after settings cancel")
|
||||
quit(1)
|
||||
return true
|
||||
_manager._quit_to_menu()
|
||||
if _frames == 290:
|
||||
if not _manager.start_menu.visible:
|
||||
|
||||
Reference in new issue
Block a user