Skip to content

The temple charges the party - #116

Merged
mmacy merged 5 commits into
mainfrom
chunk/revival-funding
Sep 15, 2026
Merged

mmacy merged 5 commits into
mainfrom
chunk/revival-funding

Conversation

@mmacy

@mmacy mmacy commented Sep 15, 2026

Copy link
Copy Markdown
Owner

PurchaseHealing charged the treated member's own purse, and nothing in the rules can put coin into a dead member's purse. That made raise_dead payable only when the corpse happened to be carrying 1,500 gp, so the documented salvage flow — game over, PlaceParty to town, raise_dead — could not be funded even by a party whose pooled wealth cleared the fee several times over. Playtests of authored adventures never once bought a raising.

The temple now charges the party. The fee comes out of the treated member's purse first and then out of the other members' purses in marching order, dead members included, and each purse is emptied in whole gold pieces through CoinPurse.spend before the next one is touched. A party whose purses together fall short is refused with items.purchase.insufficient_funds and keeps every coin: the payment is planned before any purse is opened, so the funds check stays the pure validation pre-phase it was.

HealingPurchasedEvent fills the payers and payments_gp the contract commit added, naming the purses charged and what each one put in. The default English line names the other payers only when the patient could not cover the fee alone, so the common one-payer line reads as it always did.

Second change, the first item of #108: the closed set of service names was stated twice, once as the Literal on PurchaseHealing.service and once as the keys of HEALING_SERVICES. It now has one home, the exported HealingService type in crawl/commands.py, which both use, and the price table's docstring drops the warning that the two could drift.

No draw sequence changed. tests/goldens/phase5_milestone.json is regenerated through tests/generate_phase5_goldens.py, and its diff is the two added event fields and nothing else — the milestone's patient pays alone, so the transcript line is unchanged, and the golden keeps the engine stamp of the version that produced it.

One gap this leaves: tools/docs/rejection_codes.json still describes items.purchase.insufficient_funds as more coin than "the character's purse currently holds", which is now only true for PurchaseEquipment. That file is outside this chunk's file list, so it wants a separate edit.

Verified in the worktree: pytest --runxfail tests/test_award.py tests/test_terminal_modes.py tests/test_commands.py gives 188 passed, and the full gate — ruff format --check, ruff check, pyright, pytest, mkdocs build --strict — passes with 2534 passed, 73 skipped, 3 xpassed, pyright reporting 0 errors, and a clean strict docs build. The three xpasses are this chunk's acceptance tests, whose xfail markers are still in place for the lead to remove at merge.

Closes #73

https://claude.ai/code/session_01NmCezTw8hKKujkaEZ3YGAs

HealingPurchasedEvent gains payers and payments_gp with empty defaults. Acceptance tests marked expected-to-fail: raise_dead draws on the purses in marching order, a patient who can pay pays alone, and a wiped party funds its first raising from its dead members' purses. Two unmarked guards hold today: a party one coin short is refused and keeps every coin, and the price list and the command name the same services.

Claude-Session: https://claude.ai/code/session_01NmCezTw8hKKujkaEZ3YGAs
PurchaseHealing draws the fee from the treated member's purse first, then
from the other members in marching order, dead members included, emptying
each purse in whole gold pieces before it touches the next. A party whose
purses together fall short is refused with items.purchase.insufficient_funds
and keeps every coin: planning the payment is separate from charging it, so
the funds check stays the pure pre-phase it was.

The old rule made raise_dead unbuyable in practice. The patient is a corpse,
nothing hands a corpse coin, and a party that splits its treasure never holds
1,500 gp in one purse, so the documented salvage flow — game over, PlaceParty
to town, raise dead — could not be funded by a party whose pooled wealth
cleared the fee several times over.

HealingPurchasedEvent fills the payers and payments_gp the contract added.
The default English line names the other payers only when the patient could
not cover the fee alone, which leaves the common one-payer line as it was.

The service names are now one exported type, HealingService, used by both the
command field and the HEALING_SERVICES price list, so the offered list and the
accepted list cannot drift apart; the table's docstring drops the warning that
they might.

No draw sequence changed. phase5_milestone.json is regenerated through
tests/generate_phase5_goldens.py and its diff is the two added event fields
and nothing else — the milestone's patient pays alone, so the transcript line
is unchanged, and the golden keeps its 1.7.0 engine stamp.

Claude-Session: https://claude.ai/code/session_01NmCezTw8hKKujkaEZ3YGAs
Review corrections to the prose, no behavior change.

The whole-gold rule was a refusal case no public text stated: each purse pays
in whole gold pieces, so the coin it is worth below a gold piece can never go
toward the fee, and two members holding 12 gp and 5 sp each are worth the 25 gp
price between them and are still refused. The PurchaseHealing class docstring,
its insufficient_funds bullet, and the adaptations register now state it and
carry that example.

"Each purse is emptied before the next is touched" was false and is gone from
the command docstring, the _healing_payers docstring, and the register. A purse
puts in as much of what is still owed as its gold covers, keeps what it is worth
below a gold piece, and the last purse charged pays the remainder alone.

Decision history is out: the register no longer describes the superseded rule,
and the payers field says it is empty on a log written before the field existed
rather than naming an old rule. No em dash is left in text this branch added.

_healing_payers takes a Character and returns list[tuple[Character, int]] | None,
which surfaced that Character.id is optional, so the payers tuple now uses the
id-or-name fallback that core/classes.py already uses for event ids; every party
member in a session has an id, so nothing changes. _healing_purchased takes a
HealingPurchasedEvent, imported at module scope beside the core event types.

Claude-Session: https://claude.ai/code/session_01NmCezTw8hKKujkaEZ3YGAs
…efusal

The rejection-code description for items.purchase.insufficient_funds names the party's pooled purses for a temple service.

Claude-Session: https://claude.ai/code/session_01NmCezTw8hKKujkaEZ3YGAs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The revival flow cannot be funded: raise_dead draws on the corpse's own purse, and no command can put coin into it

1 participant