fix(npc): a move applies at most once per reply
The tag extractor returns every occurrence of a move tag and the validator is
a pure membership test, so MoveApplier applied duplicates. A model repeating a
tag N times therefore picked an amount in unary — the AI choosing a number,
which is the §2 breach the bounded vocabulary exists to prevent.
The currency fix (8e1238c) closed this for denominations only. Generalizing it
to every move that names a thing covers real items too — three give_item(amulet)
tags are one amulet, not three — and lets the currency special case be deleted
rather than kept alongside a second mechanism. ID_MOVES is reused from
MoveValidator instead of restated.
Also caps adjust_disposition on the reply's NET swing. MAX_DELTA was clamped
per tag, so three +15s moved standing by 45 — precisely the wholesale swing its
own comment says it forbids. Applied in emission order, so a later
become_hostile still floors what came before it.
offer_quest and reveal needed no change: add_quest self-guards on has_quest and
mark_revealed is idempotent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
This commit is contained in:
@@ -210,10 +210,14 @@ model selecting a subset of a closed, six-element, state-gated set — an NPC ha
|
||||
a gold, a silver and a copper is a legal sentence — not inventing an integer. Unary
|
||||
repetition was what made it unbounded, and that is closed.
|
||||
|
||||
**The same duplicate-tag path exists for non-currency moves and predates this work**
|
||||
(three `give_item(amulet)` tags yield three amulets; a repeated `offer_quest` can
|
||||
double-add a quest). It is bounded *across* replies by `gifts_given` / `has_quest`, but
|
||||
not *within* one. Deliberately left alone here — it is not a currency bug. **Open.**
|
||||
**The same duplicate-tag path existed for non-currency moves and predated this work.**
|
||||
Closed in a follow-up (`fix/duplicate-move-tags`), which generalized the rule — *any*
|
||||
move that names a thing applies at most once per reply — and so **deleted** the currency
|
||||
special-case rather than leaving two mechanisms side by side. That follow-up also found
|
||||
`MAX_DELTA` was clamped **per tag**, so three `[ADJUST_DISPOSITION: +15]` tags moved
|
||||
standing by 45; the cap is now on the reply's net swing, which is what the constant's own
|
||||
comment always claimed. (`offer_quest` and `reveal` turned out to be safe already —
|
||||
`add_quest` self-guards and `mark_revealed` is idempotent.)
|
||||
|
||||
**`NpcContent.available_moves`:**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user