From 335ccf52b2799af84ede3282fce35a98aef3b07e Mon Sep 17 00:00:00 2001 From: megaproxy Date: Tue, 12 May 2026 12:17:15 +0100 Subject: [PATCH] Storyteller: restore sim speed after auto_pause event dismissal The auto_pause flag on THREAT events paused Sim when the modal fired, but resolve_current() never resumed it. Player dismisses the modal expecting play to continue; sim stays paused; pawns appear stuck. (Surfaced by first real PC playtest after the controls patch.) Now: capture Sim.current_speed before paying the pause, restore it on resolve if the player hasn't manually changed speed during the modal (current_speed != PAUSE skips restore so the player's choice wins). Field round-trips via save_dict for the save-during-modal edge case. Verified MCP runtime: lone_wolf modal fires at boot, speed=0, dismissal restores speed=1 and pawns immediately walk toward their next job. Co-Authored-By: Claude Opus 4.7 (1M context) --- autoload/storyteller.gd | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/autoload/storyteller.gd b/autoload/storyteller.gd index 8bb0ec1..38e0510 100644 --- a/autoload/storyteller.gd +++ b/autoload/storyteller.gd @@ -51,6 +51,11 @@ var _category_last_fired: Dictionary = {} # StringName → int ## Target day_index for the ghost-state wanderer auto-fire. -1 = not scheduled. var _ghost_wanderer_target_day: int = -1 +## Sim speed captured before an auto_pause event paused the sim. -1 when no +## restore is pending. Restored by resolve_current() so the player doesn't have +## to manually un-pause every threat modal dismissal. +var _speed_before_auto_pause: int = -1 + func _ready() -> void: Clock.phase_changed.connect(_on_phase_changed) @@ -90,6 +95,13 @@ func resolve_current(choice_index: int = 0) -> void: _current_event.on_resolve.call(choice_index) EventBus.storyteller_event_resolved.emit(_current_event, choice_index) _current_event = null + # Restore the sim speed if this dismissal closes an auto_pause event AND + # the player hasn't manually changed speed during the modal. (If they did, + # current_speed won't be PAUSE and we leave their choice alone.) + if _speed_before_auto_pause >= 0: + if Sim.current_speed == Sim.Speed.PAUSE: + Sim.set_speed(_speed_before_auto_pause) + _speed_before_auto_pause = -1 # ── save / load ─────────────────────────────────────────────────────────────── @@ -109,6 +121,7 @@ func save_dict() -> Dictionary: "event_last_fired": event_fired_str, "category_last_fired": cat_fired_str, "ghost_wanderer_target_day": _ghost_wanderer_target_day, + "speed_before_auto_pause": _speed_before_auto_pause, } @@ -117,6 +130,7 @@ func apply_dict(d: Dictionary) -> void: ghost_state = d.get("ghost_state", false) _last_rolled_day_index = int(d.get("last_rolled_day_index", -1)) _ghost_wanderer_target_day = int(d.get("ghost_wanderer_target_day", -1)) + _speed_before_auto_pause = int(d.get("speed_before_auto_pause", -1)) # Restore StringName keyed dicts. _event_last_fired.clear() for k: String in d.get("event_last_fired", {}): @@ -246,8 +260,13 @@ func _fire(def: EventDef, focus_tile: Vector2i = Vector2i(-1, -1)) -> void: tension = minf(100.0, tension + 15.0) EventBus.storyteller_tension_changed.emit(tension) - # Auto-pause for modal events that require it. + # Auto-pause for modal events that require it. Capture the prior speed so + # resolve_current() can restore it on dismissal — without this the sim + # stays paused after every threat modal, which the player reads as "pawns + # stopped working for no reason." if def.auto_pause: + if Sim.current_speed != Sim.Speed.PAUSE: + _speed_before_auto_pause = int(Sim.current_speed) Sim.set_speed(Sim.Speed.PAUSE) # For banners with no choices, resolve immediately after a short delay is