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>
111 lines
4.2 KiB
GDScript
111 lines
4.2 KiB
GDScript
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
|