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

@ -115,6 +115,8 @@ func tick() -> void:
_tick_pickup_corpse(t)
Toil.KIND_DEPOSIT_CORPSE:
_tick_deposit_corpse(t)
Toil.KIND_DEPOSIT_AT_WB:
_tick_deposit_at_wb(t)
if t.done:
job.advance()
@ -365,13 +367,46 @@ func _tick_deposit(t) -> void:
t.done = true
## Execute one tick of a DEPOSIT_AT_WB toil.
##
## Single-tick: transfers pawn.carried_item into the workbench's deposited_inputs
## buffer (wb.add_deposited_input) without spawning the item on the floor.
## This is leg-1 of a two-ingredient craft; leg-2 fetches ingredient2, then
## _tick_craft validates the buffer before beginning the work countdown.
##
## Fails silently (t.done = true) if workbench is gone or pawn has nothing.
func _tick_deposit_at_wb(t) -> void:
var wb_path := NodePath(t.data.get("workbench", ""))
var wb = get_tree().get_root().get_node_or_null(wb_path)
if wb == null or not is_instance_valid(wb):
Audit.log("job_runner", "%s deposit_at_wb: workbench gone — skipping" % pawn.pawn_name)
t.done = true
return
if pawn.carried_item == null:
Audit.log("job_runner", "%s deposit_at_wb: nothing to deposit" % pawn.pawn_name)
t.done = true
return
var item = pawn.carried_item
wb.add_deposited_input(item.item_type, item.stack_size)
Audit.log(
"job_runner",
"%s deposit_at_wb: %s ×%d → workbench buffer" % [pawn.pawn_name, item.item_type, item.stack_size]
)
item.queue_free()
pawn.carried_item = null
t.done = true
## Execute one tick of a CRAFT toil.
##
## First tick (started=false):
## - Resolves the Workbench node from the stored NodePath. Missing → skip.
## - Looks up the Bill at data["bill_index"]. Out-of-range → skip.
## - Validates pawn position matches workbench.tile. Mismatch → skip.
## - Validates pawn is carrying the correct ingredient type. Missing → skip.
## - For recipes with ingredient_type: validates pawn is carrying it OR (for
## two-ingredient recipes) validates wb.deposited_inputs has ingredient1
## and pawn is carrying ingredient2.
## - For no-ingredient recipes (ingredient_type == &""): no carry check.
## - Calls wb.begin_craft(bill) to register the active bill + reset progress.
## - Marks started=true and logs the craft start.
##
@ -379,7 +414,8 @@ func _tick_deposit(t) -> void:
## - Calls wb.tick_craft() which increments current_work_progress and returns
## true when bill.recipe.work_ticks is reached.
## - On completion:
## * Consumes the carried ingredient (queue_free + clear pawn slot).
## * Consumes pawn.carried_item (ingredient or ingredient2); queue_free + clear.
## * For two-ingredient recipes, also calls wb.consume_deposited_input for ing1.
## * Rolls quality via QualityCalc.roll() using the pawn's skill level.
## * Spawns an Item scene child of wb.get_parent() at wb.tile.
## * Calls wb.on_craft_complete() (records bill completion + resets state).
@ -414,13 +450,39 @@ func _tick_craft(t) -> void:
t.done = true
return
if pawn.carried_item == null or pawn.carried_item.item_type != bill.recipe.ingredient_type:
Audit.log(
"job_runner",
"%s craft: wrong or missing ingredient — skipping" % pawn.pawn_name
)
t.done = true
return
# Ingredient validation — three cases:
# (a) No primary ingredient (ingredient_type == &""): skip carry checks.
# (b) Single ingredient: pawn must be carrying ingredient_type.
# (c) Two ingredients: wb.deposited_inputs must hold ingredient1,
# pawn must be carrying ingredient2.
var has_primary: bool = bill.recipe.ingredient_type != &""
var has_secondary: bool = bill.recipe.ingredient2_type != &""
if has_primary:
if has_secondary:
# Two-ingredient path: check buffer (ing1) + carry (ing2).
if not wb.has_deposited_input(bill.recipe.ingredient_type, 1):
Audit.log(
"job_runner",
"%s craft: ingredient1 not in workbench buffer — skipping" % pawn.pawn_name
)
t.done = true
return
if pawn.carried_item == null or pawn.carried_item.item_type != bill.recipe.ingredient2_type:
Audit.log(
"job_runner",
"%s craft: wrong or missing ingredient2 — skipping" % pawn.pawn_name
)
t.done = true
return
else:
# Single-ingredient path: pawn carries ingredient1.
if pawn.carried_item == null or pawn.carried_item.item_type != bill.recipe.ingredient_type:
Audit.log(
"job_runner",
"%s craft: wrong or missing ingredient — skipping" % pawn.pawn_name
)
t.done = true
return
# Register the bill with the workbench and reset its progress counter.
wb.begin_craft(bill)
@ -450,11 +512,18 @@ func _tick_craft(t) -> void:
return
var bill = wb.bills[bill_index]
# Consume ingredient.
# Consume ingredients.
# Single-ingredient: pawn.carried_item holds the only input — free it.
# Two-ingredient: pawn.carried_item is ingredient2; ingredient1 was buffered
# in wb.deposited_inputs by _tick_deposit_at_wb — remove it from the buffer.
# No-ingredient: carried_item is null; nothing to consume.
var ingredient = pawn.carried_item
pawn.carried_item = null
if ingredient != null and is_instance_valid(ingredient):
ingredient.queue_free()
if bill.recipe.ingredient2_type != &"":
# ingredient2 was the carried item (consumed above); ingredient1 is in the buffer.
wb.consume_deposited_input(bill.recipe.ingredient_type, 1)
# Roll quality based on pawn skill for this recipe.
var skill_level: int = pawn.get_skill(bill.recipe.required_skill)