fix six critical bugs from audit sprint

save/load round-trip: workbench bills, crop static-method, bed owner,
wolf target now all survive reload via Bill.from_dict reconstruction,
_spawn_crop using setup(), and a new _post_load_resolve_references pass.

PlantProvider: sow path added; consumes 1 grain on a TILLED crop tile.

CraftingProvider: ingredient2 supported via new KIND_DEPOSIT_AT_WB toil
and Workbench.deposited_inputs buffer. Cremation pyre now actually
consumes wood.

HaulingProvider: per-item haul_retry_count + haul_rejected after 3
orphan passes; new EventBus.stockpile_layout_changed resets rejects on
any player stockpile edit.

Storyteller: 14 stubbed event effects implemented. New buff registry
(add_buff/get_buff_multiplier/has_buff, day-prune, save/load) drives
seasonal/resource events. New request_pawn_spawn signal + WANDERER
table for arrivals. New SICK status + 3 mood thoughts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-16 18:06:55 +01:00
parent 00cf8f445d
commit d9638a4ea4
19 changed files with 711 additions and 101 deletions

View file

@ -27,12 +27,20 @@ const ALERT_COOLDOWN_TICKS: int = 600
## Per-item-type cooldown map: StringName → tick at which the next emit is allowed.
var _alert_cooldown: Dictionary = {}
## Tick stamp: the last sim tick on which we incremented haul_retry_count for
## at least one orphaned item. Guards against double-counting when multiple
## pawns call find_best_for on the same tick (all share this provider instance).
var _last_orphan_tick: int = -1
func _init() -> void:
category = &"haul"
# Priority 3 — below chop (5) and mine (4); above rest (1).
# Adjusted once the full 9-category matrix is authored in Phase 17.
priority = 3
# Reset haul_rejected items whenever stockpile layout changes so a newly-
# painted stockpile or filter edit unblocks previously-rejected items.
EventBus.stockpile_layout_changed.connect(_on_stockpile_layout_changed)
# ── WorkProvider override ─────────────────────────────────────────────────────
@ -59,6 +67,10 @@ func find_best_for(pawn) -> Job:
# ── regular items ─────────────────────────────────────────────────────────
for item in World.items_needing_haul.keys():
# Skip items that already exhausted their retries — they stay out of
# haul consideration until a stockpile layout change resets them.
if item.haul_rejected:
continue
# Skip items another pawn is already carrying.
if item.being_carried:
continue
@ -79,6 +91,18 @@ func find_best_for(pawn) -> Job:
if first_orphan_type == &"":
first_orphan_type = item.item_type
first_orphan_tile = item.tile
# Increment the per-item retry counter at most once per sim tick,
# guarded by _last_orphan_tick so multiple pawn calls in the same
# tick don't multiply-count the same item.
if _last_orphan_tick < Sim.tick:
_last_orphan_tick = Sim.tick
item.haul_retry_count += 1
if item.haul_retry_count >= Item.MAX_HAUL_RETRIES:
item.haul_rejected = true
World.items_needing_haul.erase(item)
Audit.log("hauling", "item %s at %s rejected after %d retries" % [
item.item_type, item.tile, item.haul_retry_count
])
continue
var drop: Vector2i = dest.find_drop_position(item)
@ -228,3 +252,21 @@ func _destination_for_tile(tile: Vector2i):
if dest.covers_tile(tile):
return dest
return null
## Resets haul_rejected / haul_retry_count on every item so a stockpile layout
## change (new zone, filter edit, priority change) gives them a fresh chance.
## Rejected items are re-entered into items_needing_haul so sweep_for_better_
## destinations() can re-evaluate them on the next periodic pass.
func _on_stockpile_layout_changed() -> void:
var reset_count: int = 0
for item in World.items:
if item.haul_rejected or item.haul_retry_count > 0:
item.haul_rejected = false
item.haul_retry_count = 0
# Re-enqueue only if not already in the haul set and not carried.
if not item.being_carried and not World.items_needing_haul.has(item):
World.items_needing_haul[item] = true
reset_count += 1
if reset_count > 0:
Audit.log("hauling", "stockpile layout changed — reset %d rejected/retried items" % reset_count)