Skip to content

feat(temples): make church-service pricing level- and capability-aware #106

Description

@zoeyrose

Design amendment — variable-work quotes and provider debt (2026-08-13)

Important

This amendment supersedes the exact single-price and pre/postcondition
settlement assumptions below. The affordability objective, historical
evidence, newcomer protection, spell boundaries, and single authored
content@main delivery remain in force.

Provider metadata

Each provider will author:

  • a canonical npc_id, used for confirmation and provider-specific debt; and
  • a low-frequency custom key/value attribute temple_service_rank, read with
    npc.ReadKey("temple_service_rank").

The hard-coded map/name/rank registry in draft PR #211 will be removed.
temple_service_rank needs typed content-extension metadata and validation,
but no new Classic object field. The provider audit must reject missing,
duplicate, malformed, unsupported, or out-of-range identities/ranks.

Ranged quote and actual-work price

The Classic dependency
atrinik/classic#264 will expose
bounded named work components before casting and the exact work performed by
the real cast.

Content converts each work range into a monetary range using one deterministic
service formula:

  • exact work produces an exact quote;
  • uncertain work, such as resistible diseases, produces a minimum/maximum
    quote;
  • confirmation binds the work range, price range, state evidence, provider ID
    and rank, existing provider debt, and maximum possible new debt;
  • confirmation re-queries, and any drift requotes without casting;
  • the final charge is calculated from actual beneficial work, never attempted
    work or the quoted maximum;
  • zero beneficial work always means zero cash charge and zero new debt; and
  • cash plus debt can never exceed the maximum explicitly confirmed by the
    player.

This deliberately supports future per-item identification pricing. That
content migration is tracked separately in #217, while Classic's query/outcome
contract covers identification now.

Provider-specific credit

When actual work costs more than currently spendable funds, an explicitly
confirmed service may collect the available amount and record the remainder as
interest-free debt to that provider's stable npc_id.

  • The quote shows available funds and maximum possible new debt before casting.
  • A provider with an outstanding balance requires full repayment before
    offering another paid service.
  • Another provider neither collects nor blocks on that debt.
  • Eligible newcomer-essential care remains free and never creates debt.
  • Debt count, per-provider value, aggregate value, and arithmetic are bounded.
  • Provider rename/move retains identity; removal or ID replacement requires an
    explicit migration.
  • Settlement and repayment use the native bounded persistence surface in
    atrinik/classic#267, with
    reconnect, retry, save-failure, and duplicate-confirmation coverage.

Additional acceptance criteria

  • Every provider has a unique canonical npc_id and validated authored
    temple_service_rank; no runtime dispatch or debt uses a display
    name/map-path registry.
  • Every service documents named work units and a monotonic conversion from
    minimum/maximum work to minimum/maximum price.
  • The displayed range, confirmation token, actual work, cash collected, and
    debt created reconcile exactly and never exceed the confirmed maximum.
  • Tests cover exact quotes, ranged disease quotes, below/within/maximum
    actual work, zero work, fully affordable and partially funded settlement,
    existing provider debt, repayment, other-provider independence, and free
    care while indebted.
  • The issue remains blocked until all required sub-issues of
    feat(server): make scripted spell services work-aware classic#264 pass and PR feat(temples): make recovery pricing need-aware #211 is revised and revalidated at its
    latest head.

Objective

Prevent depletion, disease, poison, and related temple recovery from becoming
an unaffordable early-character trap. Define a bounded, understandable quote
that reflects the patient's need and the provider's ability, while retaining
meaningful fees for established characters.

Current behavior

The temple implementation currently on main behaves as follows:

At level 1 those prices are 5 silver 90 copper for depletion, 30 silver for a
curse, 50 silver for damnation, 10 silver for disease, and 5 silver for poison.
The starter inventory contains no currency, and the Incuna opening exposes the
full service menu through level-20 priest Brelend Lee. One early quest outcome
awards only 50 copper; even its 15-silver combat outcome does not cover every
service.

Classic already models some of the requested difficulty relationship, but the
price ignores it:

  • a disease resists according to disease level versus caster/NPC level;
  • remove curse/damnation skips items above the caster's effective level; and
  • remove depletion and cure poison currently ignore caster and patient levels.

The content script pays before casting, while Classic's Python Cast() binding
discards the native success result. A weak priest can therefore fail or only
partially cure a condition after taking the same flat fee as a stronger priest;
the same path can also charge for a no-op when there is nothing applicable to
cure.

Normal death has a narrow existing safeguard: Classic does not apply death
depletion through character level 3. That does not protect against disease,
poison, curses, cursed depletion potions, or later level-4 depletion, and the
depletion tooltip now explicitly directs affected players to the priest
service (atrinik/atrinik#144).

Historical intent

This is not a new concept. The original shared Temple implementation made
remove-depletion, cure-disease, and cure-poison services free below level 3 and
used player-level prices above that boundary
(commit f95655f).
A 2011 dialog refactor removed the newcomer grace without a balance rationale
(commit 9c02264),
and the 2014 InterfaceBuilder rewrite later flattened disease and poison to the
current fixed prices
(commit f61c545).

The old numbers should not be restored blindly, but they establish that both a
newcomer grace band and patient-level pricing were intentional behavior that
drifted during unrelated refactors.

Proposed direction

  1. Centralize service metadata and quote calculation so preview and confirmation
    use exactly the same bounded integer price.
  2. Establish and document a newcomer subsidy/free band for essential recovery.
    Start by evaluating the historical level-3 boundary against the current
    economy and the server's existing death-depletion grace. Apply it at least to
    depletion, disease, and poison; assess curse and damnation separately because
    the historical rule deliberately excluded inventory curse removal.
  3. Make later prices monotonic in actual need (patient level and, where
    available, affliction/item level or depletion severity) and lower when a
    capable provider treats an easy case. A provider that cannot reliably affect
    the condition should refuse it or clearly quote the defined failure/partial
    policy rather than selling it as equivalent to a guaranteed cure.
  4. Audit all temple providers before choosing the capability input. Their combat
    levels currently range from an effective 1 to 115 and several use guard,
    ogre, or other non-priest archetypes, so raw NPC level is not automatically a
    stable priest-proficiency contract. Prefer an explicit authored service rank
    if that audit cannot justify using combat level.
  5. Define settlement for no condition, total failure, and partial success. Do
    not charge for a no-op. If paid-on-success needs the native result, add a
    linked atrinik/classic change to return the cast result (with a compatibility
    audit) or use a proven synchronous pre/postcondition check.
  6. Preserve the existing spell boundaries: do not broaden restoration to
    remove depletion or collapse disease, poison, curses, and depletion into one
    generic cleanse.

Plan alignment

This should be the owning balance/design issue consumed by the future stack:

The completed behavior inventories in #41 and atrinik/server#4 assign
Temple.py to the replacement temple domain, but neither settled this balance
policy. No existing live issue or pull request was found that owns it.

Acceptance criteria

  • The exact newcomer boundary, full-price curve, capability input, rounding,
    minimum, and maximum are documented with economy/playtest evidence.
  • A fresh/low-level character can obtain essential depletion, disease, and
    poison recovery without an early progression deadlock.
  • Quotes cover below/at/above-boundary patients and weak/equal/strong
    providers, including high-level conditions presented to an incapable
    provider.
  • The displayed quote and amount charged cannot drift between preview and
    confirmation.
  • No-condition, insufficient-funds, full failure, partial success, repeat
    confirmation, and rounding/cap behavior are explicit and tested.
  • All standard and special temple providers are inventoried; incidental NPC
    combat levels are not silently treated as priest proficiency.
  • Free/discounted care cannot be used to subsidize an arbitrarily difficult
    high-level condition without an explicit policy decision.
  • restoration and the individual cure spell scopes remain unchanged.
  • The authored fix lands only on content@main; no parallel authored copy or backport is planned. Classic consumes the deterministic Classic target derived from that same reviewed main revision.
  • main passes python3 tools/validate.py and git diff --check; its derived Classic target is exercised in an isolated Classic scenario with low/high patients, low/high-capability providers, success/failure, and cleanup.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Fields

Priority

None yet

Start date

None yet

Target date

None yet

Effort

None yet

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions