Clarify NPC mention eligibility

This commit is contained in:
2026-08-06 13:40:42 +00:00
parent 4192aa8584
commit 1015d61b2d
7 changed files with 33 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
Extract Dungeons & Dragons NPC occurrence occurrences from the supplied
Extract Dungeons & Dragons NPC occurrences from the supplied
transcript. Include an occurrence only when the transcript establishes one
supplied NPC, one occurrence kind, and a coherent passage supporting both.
Use the exact `npc_id` and matching `name` pair from the supplied NPC registry;

View File

@@ -1,15 +1,19 @@
Extract the individually identifiable Dungeons & Dragons non-player characters
established by the provided transcript.
Include an in-world non-PC participant only when the transcript gives it a
proper name or a stable, individually distinguishing title or alias.
Include an in-world non-PC only when the transcript factually establishes a
proper name or a stable, individually distinguishing title or alias. A factual
third-party mention establishes that identity even when the NPC is not
physically present, does not speak, and takes no direct action in this chunk.
Record only the NPC identity and the transcript evidence that establishes it;
do not infer or classify a separate occurrence.
Exclude human players, transcript speakers, and the GM as out-of-world people;
player characters identified by the player or party references; incidental or
hypothetical name drops; corrected transcription mistakes; anonymous or generic
roles; indistinguishable crowds or groups; invented descriptive labels; and
temporary summoned creatures or spell effects without a persistent individual
identity.
player characters identified by the player or party references; names used only
in hypothetical, speculative, or imagined examples; corrected transcription
mistakes; anonymous or generic roles; indistinguishable crowds or groups;
invented descriptive labels; and temporary summoned creatures or spell effects
without a persistent individual identity.
Preserve observed display spelling. Do not invent a label for an anonymous
creature, crowd, or generic role.

View File

@@ -71,6 +71,9 @@ The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) r
the exact `{npc_id, name}` pair. Unknown IDs and names that do not match their
ID are rejected; normalization does not repair names by similarity. Registry
references are provenance only and never replace an occurrence's own evidence.
The registry may include an identity established by a factual third-party
mention; that provenance alone does not create a `mentioned` occurrence. Each
occurrence remains a separately cited fact in the current transcript.
Normalization validates the exact pair, orders and
deduplicates exact source references, then orders occurrences by valid source
chronology, NPC comparison identity, display name, kind, and reference sequence.

View File

@@ -68,10 +68,16 @@ descriptor.
## Scope and consumers
Only individually identifiable NPC names with transcript evidence belong in
this artifact. Groups, generic roles, invented labels, and descriptive
enrichment are excluded. Its source references prove registry provenance; they
do not become evidence for a spell, occurrence, combat, or enemy-event
occurrence.
this artifact. A factual third-party mention can establish an identity even if
the NPC is not present, speaking, or acting in the cited passage. Names used
only in hypothetical, speculative, or imagined examples are excluded, as are
groups, generic roles, invented labels, and descriptive enrichment. Its source
references prove registry provenance; they do not become evidence for a spell,
occurrence, combat, or enemy-event occurrence.
Registry evidence establishes an identity, not an [NPC occurrence](dnd-npc-occurrence-artifacts.md).
That later artifact independently records any current-transcript occurrence
with its own cited evidence and category.
This registry can ground actor or caster names in the [spell](dnd-spell-artifacts.md)
and [combat-turn](dnd-combat-turn-artifacts.md) artifacts. It is required to

View File

@@ -168,11 +168,11 @@ checkpoint fingerprint.
| Lane | Intentional behavior |
| --- | --- |
| Spells | May use a spell-catalog overlay and optional NPC grounding; the catalog validator supplies domain-specific semantic checks. |
| NPC registry | Does not consume an NPC registry. Its normalizer is the LLM-assisted reconciliation exception described above. |
| NPC registry | Establishes transcript-grounded NPC identities, including factual third-party mentions, without assigning occurrence categories. It does not consume an NPC registry, and its normalizer is the LLM-assisted reconciliation exception described above. |
| Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. |
| Item occurrences | Requires the normalized item registry for exact ID/name grounding at extraction and normalization. Campaign context may disambiguate, but the registry never becomes occurrence evidence. |
| Item registry | Produces source-grounded item types and unique designations. Its LLM-assisted reconciliation is proposal-only, preserves distinct currency denominations and item types, and does not create per-instance identities. |
| NPC occurrences | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
| NPC occurrences | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. It separately emits cited current-transcript occurrence facts, including `mentioned`, rather than deriving them from registry provenance. |
| Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. |
| Enemy events | Requires NPC, scene-description, combat-turn, and NPC-occurrence artifacts. It calls the LLM only for an exact `combat` classification, records ordered observations rather than terminal state, and normalizes recognized names through the NPC registry while preserving grounded collective labels. |
| Location registry | Produces a source-anchored, session-scoped registry from stable proper names or unique in-world designations. Its LLM-assisted reconciliation is proposal-only and never collapses same-name places without validated identity and evidence rules. |

View File

@@ -220,7 +220,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
if err != nil {
t.Fatal(err)
}
interactionContent, err := occurrencecodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
occurrenceContent, err := occurrencecodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
{NPCID: identity.DeriveID(enemy), Name: enemy, Kind: dnd.NPCOccurrenceKindCombatOpponent, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 1, EndUnitID: 1}}},
{NPCID: identity.DeriveID("Aria"), Name: "Aria", Kind: dnd.NPCOccurrenceKindCombatAlly, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 2, EndUnitID: 2}}},
}})
@@ -231,7 +231,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
NPCRegistryReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCRegistryReferenceSlot, npcContent)}},
SceneDescriptionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(SceneDescriptionReferenceSlot, sceneContent)}},
CombatTurnReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(CombatTurnReferenceSlot, turnContent)}},
NPCOccurrenceReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCOccurrenceReferenceSlot, interactionContent)}},
NPCOccurrenceReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCOccurrenceReferenceSlot, occurrenceContent)}},
}}
}

View File

@@ -10,7 +10,7 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
occurrencemodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
@@ -107,7 +107,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
func allSourceRefsValid(index source.DocumentIndex, value dnd.NPCOccurrenceList) bool {
for _, occurrence := range value.Occurrences {
if !interactionmodel.ValidSourceRefs(index, occurrence.SourceRefs) {
if !occurrencemodel.ValidSourceRefs(index, occurrence.SourceRefs) {
return false
}
}
@@ -136,13 +136,13 @@ func issuesFor(order shared.SourceRefOrder, value dnd.NPCOccurrenceList, npcRegi
}
if !sort.SliceIsSorted(value.Occurrences, func(left, right int) bool {
return interactionmodel.Less(order, value.Occurrences[left], value.Occurrences[right])
return occurrencemodel.Less(order, value.Occurrences[left], value.Occurrences[right])
}) {
issues = append(issues, "occurrences are not in canonical order")
}
seen := make(map[string]int)
for index, occurrence := range value.Occurrences {
key := interactionmodel.ExactIdentity(occurrence)
key := occurrencemodel.ExactIdentity(occurrence)
if previous, ok := seen[key]; ok {
issues = append(issues, fmt.Sprintf("occurrences[%d] duplicates occurrence %d", index, previous))
continue