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

@ -269,6 +269,10 @@ func _ready() -> void:
# autoloads are fully mounted (trigger predicates read Clock, World, Storyteller).
EVENT_CATALOG_SCRIPT.register_all(Storyteller)
# Phase 17 — wanderer events spawn new pawns via EventBus so EventCatalog
# stays static (no get_node() into the scene tree).
EventBus.request_pawn_spawn.connect(_on_request_pawn_spawn)
# Phase 4: every 5 in-game seconds (100 ticks), re-evaluate items in
# stockpiles in case a higher-priority destination opened up.
EventBus.sim_tick.connect(_on_sim_tick_world_sweep)
@ -436,6 +440,48 @@ func _spawn_sample_pawns() -> void:
World.register_pawn(p)
## Phase 17 — Wanderer event handler: instantiate a new pawn at the map entry
## point (top-left area) with optional seeded skills from `skills` dict.
## Called via EventBus.request_pawn_spawn emitted by EventCatalog helpers.
## Skills dict supports the same keys as Pawn.SKILL_* constants:
## "crafting", "cooking", "medicine", "combat", "manual_labor"
const WANDERER_NAMES: Array[String] = [
"Aldric", "Maren", "Sven", "Tilda", "Piers", "Wren", "Gareth", "Isolde",
"Finn", "Runa", "Oswin", "Elke", "Bertram", "Sigrid", "Leofric", "Astrid",
]
const WANDERER_SPAWN_TILE: Vector2i = Vector2i(5, 5) # map entry corner
func _on_request_pawn_spawn(skills: Dictionary) -> void:
# Pick a name not already in use.
var used_names: Array = []
for p in World.pawns:
used_names.append(p.pawn_name)
var available: Array[String] = []
for n in WANDERER_NAMES:
if not used_names.has(n):
available.append(n)
var chosen_name: String = available[randi() % available.size()] if not available.is_empty() else "Wanderer"
var p: Pawn = PAWN_SCENE.instantiate()
add_child(p)
p.setup(chosen_name, WANDERER_SPAWN_TILE)
var jr := JobRunner.new()
jr.name = "JobRunner"
p.add_child(jr)
jr.setup(p, pathfinder)
p.job_runner = jr
# Seed any skills specified by the event (e.g. combat: 8 for old soldier).
for skill_key in skills:
var sn: StringName = StringName(skill_key)
if sn in Pawn.ALL_SKILLS:
p.set_skill(sn, int(skills[skill_key]))
World.register_pawn(p)
Audit.log("world", "wanderer spawned: '%s' at %s skills=%s" % [chosen_name, WANDERER_SPAWN_TILE, str(skills)])
# ── Phase 4: harvestables (stockpile-zone seeding removed 2026-05-15) ───────
func _spawn_sample_harvestables() -> void: