Alerts: wire room_too_large, no_stockpile_accepts, bill_blocked

Three alert signals had no UI subscribers — gameplay failures vanished
silently. Now all three feed AlertsLog via translator handlers that
forward to the generic alert_added sink.

- EventBus: new no_stockpile_accepts(item_type, tile) and
  bill_blocked(recipe_label, reason, focus_tile) signals.
- HaulingProvider: per-item-type 30s cooldown; emits when find_best_for
  scan finishes with viable items but no destinations.
- CraftingProvider: per-(workbench, reason) 60s cooldown; emits at the
  skill_too_low and missing_ingredient continue sites. no_workbench
  reason declared for future use but not emitted (the iteration shape
  has no natural site for it).
- AlertsLog: connect + disconnect for all three signals using the same
  has_signal-guarded pattern; translator handlers convert to localized
  alert_added(severity, text, focus_tile).
- AlertsLog catch-up: room_too_large emits during World init, before
  this CanvasLayer mounts. _catch_up_room_too_large() in _ready scans
  World.rooms for rooms > ROOM_AUTOROOF_CAP and replays them, so the
  pre-built cabin's 24-tile-too-large warning lands in the log on every
  boot. Hauling/bill signals fire at runtime so they need no catch-up.

Verified runtime: cabin warning shows up in AlertsLog with severity
'warn' and focus_tile (45, 24) — the cabin top-left.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-12 13:16:25 +01:00
parent 335ccf52b2
commit 708080a022
4 changed files with 115 additions and 0 deletions

View file

@ -73,6 +73,16 @@ func _ready() -> void:
EventBus.storyteller_event_fired.connect(_on_storyteller_event)
if EventBus.has_signal("day_ended"):
EventBus.day_ended.connect(_on_day_ended)
if EventBus.has_signal("room_too_large"):
EventBus.room_too_large.connect(_on_room_too_large)
# Catch-up: room_too_large emits during World init (cabin demo room),
# which runs before this CanvasLayer mounts. Replay existing too-large
# rooms now so the boot-state cabin warning shows up in the log.
_catch_up_room_too_large()
if EventBus.has_signal("no_stockpile_accepts"):
EventBus.no_stockpile_accepts.connect(_on_no_stockpile_accepts)
if EventBus.has_signal("bill_blocked"):
EventBus.bill_blocked.connect(_on_bill_blocked)
Audit.log("alerts_log", "AlertsLog ready")
@ -84,6 +94,12 @@ func _exit_tree() -> void:
EventBus.storyteller_event_fired.disconnect(_on_storyteller_event)
if EventBus.has_signal("day_ended") and EventBus.day_ended.is_connected(_on_day_ended):
EventBus.day_ended.disconnect(_on_day_ended)
if EventBus.has_signal("room_too_large") and EventBus.room_too_large.is_connected(_on_room_too_large):
EventBus.room_too_large.disconnect(_on_room_too_large)
if EventBus.has_signal("no_stockpile_accepts") and EventBus.no_stockpile_accepts.is_connected(_on_no_stockpile_accepts):
EventBus.no_stockpile_accepts.disconnect(_on_no_stockpile_accepts)
if EventBus.has_signal("bill_blocked") and EventBus.bill_blocked.is_connected(_on_bill_blocked):
EventBus.bill_blocked.disconnect(_on_bill_blocked)
# ── public API ────────────────────────────────────────────────────────────────
@ -254,6 +270,52 @@ func _on_day_ended(summary: Dictionary) -> void:
Audit.log("alerts_log", "day_ended logged: %s" % text)
## Boot catch-up: scan World.rooms for rooms exceeding the auto-roof cap and
## replay them through the translator. RoomDetector emits room_too_large during
## World init, before this CanvasLayer mounts; without this catch-up the cabin
## warning (always present at boot in the demo seed) never reaches the log.
func _catch_up_room_too_large() -> void:
for id in World.rooms:
var r = World.rooms[id]
if r != null and r.tile_count() > Room.ROOM_AUTOROOF_CAP:
_on_room_too_large(r.bounds.position, r.tile_count())
## Translates room_too_large → alert_added (warn).
func _on_room_too_large(top_left: Vector2i, cell_count: int) -> void:
EventBus.alert_added.emit(
&"warn",
"Room too large to roof (%d tiles). Split with an interior wall." % cell_count,
top_left,
)
Audit.log("alerts_log", "room_too_large translated: %d tiles at %s" % [cell_count, top_left])
## Translates no_stockpile_accepts → alert_added (warn).
func _on_no_stockpile_accepts(item_type: StringName, tile: Vector2i) -> void:
EventBus.alert_added.emit(
&"warn",
"No stockpile accepts %s" % String(item_type),
tile,
)
Audit.log("alerts_log", "no_stockpile_accepts translated: %s at %s" % [String(item_type), tile])
## Translates bill_blocked → alert_added (warn).
func _on_bill_blocked(recipe_label: String, reason: StringName, focus_tile: Vector2i) -> void:
var reason_str: String = {
&"missing_ingredient": "missing ingredient",
&"skill_too_low": "skill too low",
&"no_workbench": "no workbench reachable",
}.get(reason, String(reason))
EventBus.alert_added.emit(
&"warn",
"Bill blocked: %s%s" % [recipe_label, reason_str],
focus_tile,
)
Audit.log("alerts_log", "bill_blocked translated: %s%s" % [recipe_label, reason_str])
# ── helpers ───────────────────────────────────────────────────────────────────
func _format_timestamp() -> String: