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

@ -44,6 +44,13 @@ var _path: Array[Vector2i] = []
var _step_progress: float = 0.0
var _attack_cooldown: int = 0
## Transient: set by from_dict() to the saved target's pawn_name string.
## SaveSystem._post_load_resolve_references() walks World.pawns, matches by
## pawn_name, assigns target_pawn, then clears this field.
## If the named pawn no longer exists the field stays "" and target_pawn stays
## null — the AI will pick a new target on the next sim tick.
var _pending_target_name: String = ""
# ── lifecycle ────────────────────────────────────────────────────────────────
@ -207,9 +214,11 @@ func from_dict(d: Dictionary) -> void:
state = int(d.get("state", State.APPROACH)) as State
_step_progress = float(d.get("step_progress", 0.0))
_attack_cooldown = int(d.get("attack_cooldown", 0))
# target_pawn: re-resolved by the loader after all pawns are restored.
# Store the name in a temporary string; caller sets target_pawn post-load.
target_pawn = null # caller must re-resolve from "target_pawn_name"
# target_pawn is re-wired by SaveSystem._post_load_resolve_references() after
# all pawns are spawned. Store the name for that pass; if the pawn no longer
# exists target_pawn stays null and the AI picks a new target next tick.
target_pawn = null
_pending_target_name = str(d.get("target_pawn_name", ""))
_path.clear()
for entry in d.get("path", []):
if entry is Array and entry.size() == 2: