wire buff consumers, sick penalty, multi-count cremation

A: Storyteller.multiply_drops() stochastic-rounding helper drives
crop_growth, harvest_yield, chop, mine consumption sites. sleep_decay
multiplied in Pawn sleep tick.

B: Pawn._sick_speed_penalty() (0% healthy → 75% severity 3, clamped to
25% min speed). JobRunner._work_speed_mult coin-flips per-tick progress
on INTERACT/BUILD/CRAFT toils. Sleep/eat/treat unaffected.

C: CraftingProvider builds N deposit trips for ingredient2_count > 1.
JobRunner._tick_craft validates+consumes the full count from buffer.
Cremation now actually requires and consumes 5 wood.

crop._stage_accum round-trips through save/load to preserve buff-
accumulated fractional growth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-16 18:17:29 +01:00
parent d9638a4ea4
commit 2afca16299
9 changed files with 156 additions and 46 deletions

View file

@ -49,6 +49,10 @@ const _STAGE_H: int = 32
var stage: Stage = Stage.SOWN
## Progress within the current growth stage; 0..STAGE_TICKS.
var stage_progress: int = 0
## Float accumulator for sub-tick growth when crop_growth buff is active.
## Carries fractional progress between ticks so the buff has the correct
## expected-value effect even though stage_progress is stored as int.
var _stage_accum: float = 0.0
# Phase 13 — "no growth indoors" rule. True once we've logged the first
# indoor detection for this crop instance so we don't flood the audit log.
@ -106,9 +110,11 @@ func on_harvest_tick() -> void:
if not is_harvestable():
return
var item_type := _harvest_output_for(crop_kind)
var base_qty: int = 1
var drop_qty: int = Storyteller.multiply_drops(base_qty, Storyteller.get_buff_multiplier(&"harvest_yield"))
var it: Item = ITEM_SCENE.instantiate()
get_parent().add_child(it)
it.setup(item_type, 1, tile)
it.setup(item_type, drop_qty, tile)
# Carry the crop_kind through as a visual subtype so wheat / corn /
# potato / strawberry each render distinctly, while item_type stays
# generic (TYPE_GRAIN / TYPE_VEGETABLE) for stockpile filter purposes.
@ -149,7 +155,13 @@ func _on_sim_tick(_n: int) -> void:
# Crop has moved outdoors or was never indoors — reset the log flag so a
# future re-roofing produces another audit line.
_logged_indoor = false
stage_progress += 1
# Apply crop_growth buff: accumulate fractional progress each tick so the
# expected-value growth rate exactly matches the multiplier.
var growth_mult: float = Storyteller.get_buff_multiplier(&"crop_growth")
_stage_accum += growth_mult
var whole: int = int(floor(_stage_accum))
_stage_accum -= float(whole)
stage_progress += whole
if stage_progress >= STAGE_TICKS:
stage_progress = 0
stage = (int(stage) + 1) as Stage
@ -169,6 +181,7 @@ func to_dict() -> Dictionary:
"crop_kind": String(crop_kind),
"stage": int(stage),
"stage_progress": stage_progress,
"stage_accum": _stage_accum,
}
@ -182,6 +195,7 @@ static func from_dict(d: Dictionary) -> Dictionary:
"crop_kind": StringName(d.get("crop_kind", "wheat")),
"stage": int(d.get("stage", Stage.SOWN)),
"stage_progress": int(d.get("stage_progress", 0)),
"stage_accum": float(d.get("stage_accum", 0.0)),
}