rimlike/scenes/ai/recipe_catalog.gd
megaproxy 67ec2cce7f Phase 14: Death + Corpses + Burial + Cremation
Three-agent fan-out. Opus pre-wrote Corpse class + 5 EventBus signals +
World registries (corpses, grave_markers) before dispatch so all three
slices ran fully parallel. Pattern proven across Phases 12/13/14.

Death pipeline (Agent A):
- Pawn.is_dead(), _check_death() — pawn_died signal → corpse spawn →
  corpse_spawned signal → World.unregister_pawn → queue_free
- _last_damage_source carries cause from take_damage() (now StringName)
- Bleed-out timeout: _bleed_ticks accumulates while bleeding active;
  at BLEED_OUT_TICKS=432000 (6 in-game hours) force-kills via take_damage
- Pawn.portrait_color stored field for corpse head-color hand-off
- Corpse: DECAY_PER_TICK=0.05 (~33 in-game min fresh→rotted at 1×),
  is_rotting()@50, queue_free@100 with corpse_rotted_away signal.
  Rotting bumps DirtinessSystem (Phase 13 hook) +0.04/tick (~+8/in-game-min)
- DEMO_PHASE14_AUTOKILL toggle in world.gd (default false, gates safety)

Graveyard + GraveSlot + GraveMarker + Hauling (Agent B):
- scenes/world/graveyard_zone.gd — StorageDestination subclass,
  accepted_types=[corpse], brownish overlay, finds dug GraveSlots
- scenes/entities/grave_slot.gd — buildable (ghost→dug) state machine,
  StorageDestination duck-type interface, accept_corpse() spawns
  GraveMarker + emits corpse_buried + queue_frees self
- scenes/entities/grave_marker.gd — permanent memorial, procedural
  stone-cross _draw, carries deceased identity, save round-trip
- TOOL_GRAVEYARD + TOOL_DIG_GRAVE paint modes (Designation dispatch)
- KIND_PICKUP_CORPSE + KIND_DEPOSIT_CORPSE toils + JobRunner handlers
- HaulingProvider.find_best_for iterates World.corpses in addition to
  items_needing_haul; corpse-payload stored as Node metadata on pawn
- ConstructionProvider duck-type already accepts GraveSlot (no change)

Cremation + Ash + Mood thoughts (Agent C):
- scenes/entities/cremation_pyre.gd — extends Workbench, label 'Pyre',
  auto-populates FOREVER bill for cremate_corpse, on_craft_complete
  drops 1 ash + emits corpse_cremated + queue_frees corpse
- Recipe.ingredient2_type/count added with save round-trip; recipe
  catalog entry cremate_corpse(TYPE_CORPSE primary + 5 wood secondary)
  NOTE: CraftingProvider still only enforces ingredient1 — documented
  gap, ships when crafting is generalized.
- Item.TYPE_ASH added + ALL_TYPES filter array entry
- 4 mood thoughts: saw_corpse (-3 EVENT 1200t max=3), buried_friend
  (+2 EVENT 2400t), cremated_friend (+2 EVENT 2400t),
  rotting_body_in_colony (-4 PERSISTENT stacks=count capped at 3)
- Pawn sync hooks: proximity scan (saw_corpse), signal listeners
  (buried/cremated within 8-tile radius), count helper for rotting

MCP runtime verified:
- DEMO_PHASE14_AUTOKILL toggle force-killed Bram at tick 50
- 'Bram DIED (cause=demo_kill, tile=(20, 36))' + corpse spawned
- 'Cora: saw_corpse thought added (corpse Bram at dist 5)' — mood -3
- Painted graveyard + dig_grave → grave dug to completion verified
  in build_queue (grave @(22, 39) complete=true)
- Hauler round-trip (corpse → GraveSlot → GraveMarker) WIRED correctly
  but didn't land within decay window at ULTRA speed (12×) — corpse
  rotted before priority-3 corpse-haul scheduled. Tuning for Phase 20.
- Screenshot captured: fresh corpse silhouette at cabin doorway

Delegation: 3× gdscript-refactor (Sonnet) agents in parallel;
integration + MCP runtime verify on Opus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:48:15 +01:00

111 lines
4.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name RecipeCatalog
## Static registry of all recipes. Each method returns a fresh Recipe instance
## configured for that product. Extend here as new workbenches land.
##
## Phase 6 — two recipes:
## plank() — wood → plank (Carpenter's bench, Crafting)
## stone_block() — stone → stone_block (Smelter, Crafting)
##
## Phase 7 — cooking chain (grain → flour → bread, vegetable → meal):
## flour() — grain → flour (Millstone, Crafting)
## bread() — flour → bread (Hearth, Cooking)
## meal_from_vegetables() — vegetable → meal (Hearth, Cooking)
##
## The full ~22-recipe list per docs/design.md continues in Phase 8+.
static func plank() -> Recipe:
var r := Recipe.new()
r.id = &"plank"
r.label = "Wood plank"
r.ingredient_type = Item.TYPE_WOOD
r.output_type = Item.TYPE_PLANK
r.work_ticks = 60 # ~3 sim seconds at 1× (20 Hz × 3 s = 60 ticks)
r.required_skill = Recipe.SKILL_CRAFTING
r.skill_threshold = 0
return r
static func stone_block() -> Recipe:
var r := Recipe.new()
r.id = &"stone_block"
r.label = "Stone block"
r.ingredient_type = Item.TYPE_STONE
r.output_type = Item.TYPE_STONE_BLOCK
r.work_ticks = 80 # ~4 sim seconds at 1×
r.required_skill = Recipe.SKILL_CRAFTING
r.skill_threshold = 0
return r
# ── Phase 7 — cooking chain ───────────────────────────────────────────────────
static func flour() -> Recipe:
## Step 1 of the grain→flour→bread chain.
## Ingredient: grain. Output: flour. Worked at the Millstone (accepted_skill = crafting).
## Any crafter can do this — no culinary expertise required for grinding.
var r := Recipe.new()
r.id = &"flour"
r.label = "Flour"
r.ingredient_type = Item.TYPE_GRAIN
r.output_type = Item.TYPE_FLOUR
r.work_ticks = 50 # ~2.5 sim seconds at 1× — quick grinding pass
r.required_skill = Recipe.SKILL_CRAFTING
r.skill_threshold = 0
return r
static func bread() -> Recipe:
## Step 2 of the grain→flour→bread chain.
## Ingredient: flour. Output: bread. Worked at the Hearth (accepted_skill = cooking).
var r := Recipe.new()
r.id = &"bread"
r.label = "Bread"
r.ingredient_type = Item.TYPE_FLOUR
r.output_type = Item.TYPE_BREAD
r.work_ticks = 90 # ~4.5 sim seconds at 1× — baking takes longer
r.required_skill = Recipe.SKILL_COOKING
r.skill_threshold = 0
return r
static func meal_from_vegetables() -> Recipe:
## Single-step cooking: vegetable → meal. Worked at the Hearth (accepted_skill = cooking).
## Parallel path so harvested produce (potatoes, etc.) can reach the belly
## without going through the grain chain.
var r := Recipe.new()
r.id = &"meal_veg"
r.label = "Veggie meal"
r.ingredient_type = Item.TYPE_VEGETABLE
r.output_type = Item.TYPE_MEAL
r.work_ticks = 80 # ~4 sim seconds at 1×
r.required_skill = Recipe.SKILL_COOKING
r.skill_threshold = 0
return r
# ── Phase 14 — Death + cremation ─────────────────────────────────────────────
static func cremate_corpse() -> Recipe:
## Cremation recipe. Primary ingredient: 1 corpse (TYPE_CORPSE).
## Secondary ingredient: 5 wood (ingredient2_type / ingredient2_count) — see
## recipe.gd for the Phase 14 extension fields.
## Output: 1 ash item (TYPE_ASH). Work time: 60 ticks (~3 sim seconds at 1×).
## No Quality roll — cremation is binary. accepted_skill = manual_labor;
## any laborer can operate the pyre.
##
## Stub gap: CraftingProvider's ingredient-pickup step only handles the
## primary ingredient (ingredient_type = TYPE_CORPSE). The 5-wood secondary
## requirement is recorded in ingredient2_type/ingredient2_count but is NOT
## enforced at runtime until CraftingProvider is extended (Phase 14 follow-up).
var r := Recipe.new()
r.id = &"cremate_corpse"
r.label = "Cremate corpse"
r.ingredient_type = Item.TYPE_CORPSE
r.ingredient2_type = Item.TYPE_WOOD
r.ingredient2_count = 5
r.output_type = Item.TYPE_ASH
r.work_ticks = 60 # ~3 sim seconds at 1×
r.required_skill = &"manual_labor"
r.skill_threshold = 0
return r