Tighten NPC interaction validation and consistency

This commit is contained in:
2026-07-23 14:25:32 +00:00
parent 36e0512454
commit bfe25609a7
14 changed files with 426 additions and 278 deletions

View File

@@ -387,8 +387,8 @@ production validators do not call the LLM and must not set `llm_profile`.
| `normalize/dnd/combat-turns/invariants` | deterministic | Rejects normalized combat-turn identity, evidence-order, and chronology violations. |
| `extract/dnd/npc-interactions/shape` | deterministic | Rejects malformed D&D NPC-interaction-list artifacts. |
| `extract/dnd/npc-interactions/registry` | deterministic | Rejects interaction names absent from the supplied NPC registry. |
| `extract/dnd/npc-interactions/source_refs` | deterministic | Rejects missing or invalid D&D interaction source references. |
| `extract/dnd/npc-interactions/source_relatedness` | deterministic | Emits warnings when an interaction name is not found near cited source text. |
| `extract/dnd/npc-interactions/source_refs` | deterministic | Rejects missing, invalid, or extract-chunk-external D&D interaction source references. |
| `extract/dnd/npc-interactions/source_relatedness` | deterministic | Emits bounded warnings when an interaction name is not found in its cited source text. |
| `normalize/dnd/npc-interactions/invariants` | deterministic | Rejects normalized interaction identity, evidence-order, and chronology violations. |
The production default chain for `dnd/spells` is used for both its extract and
@@ -460,9 +460,9 @@ normalize:
- generic/valid_json
- extract/dnd/npc-interactions/shape
- extract/dnd/npc-interactions/registry
- normalize/dnd/npc-interactions/invariants
- extract/dnd/npc-interactions/source_refs
- generic/valid_json_schema
- normalize/dnd/npc-interactions/invariants
- extract/dnd/npc-interactions/source_relatedness
```

View File

@@ -33,23 +33,57 @@ array may be empty. Each item has exactly `name`, `kind`, and `source_refs`:
`name` is the canonical display name from the required NPC registry.
`source_refs` contains one or more current-source ranges with required
`source_id`, `start_unit_id`, and `end_unit_id`; unit IDs are positive integers.
During extraction, every range must be wholly contained in the current accepted
chunk. This prevents a candidate from citing valid units that were not presented
to that extraction call.
Unknown fields are rejected.
## Interaction Categories
`kind` is exactly one of:
- `mentioned`: the named NPC is referenced without stronger participation.
- `noncombat_presence`: the NPC is present in the current scene without a
dialogue or combat classification.
- `dialogue`: the NPC participates in spoken interaction.
- `combat_ally`: the NPC participates in combat aligned with the party.
- `combat_opponent`: the NPC participates in combat against the party.
- `other`: a transcript-supported interaction outside the bounded categories.
| Kind | Meaning |
| --- | --- |
| `mentioned` | The NPC is referred to, but is not established as present or communicating in the evidenced passage. |
| `noncombat_presence` | The NPC is present and relevant to the passage but does not meaningfully participate in dialogue or combat. |
| `dialogue` | The NPC speaks, responds, or is directly engaged in a meaningful non-combat exchange. |
| `combat_ally` | The NPC actively participates in combat on the party's side. |
| `combat_opponent` | The NPC actively participates in combat against the party. |
| `other` | The transcript clearly establishes a direct NPC occurrence that fits none of the preceding kinds. |
`other` is a residual category for positively evidenced activity, not a fallback
for uncertain classification. When activities overlap, active combat
participation outranks dialogue, presence, and mention; dialogue outranks
non-combat presence and mention; and non-combat presence outranks mention.
Combat alignment is not resolved by precedence: a meaningful change between
ally and opponent creates separate occurrences.
These categories do not encode summaries, relationships, state, motives, or
unobserved events.
## Occurrence Boundaries And Ordering
One occurrence represents one NPC, one kind, and one locally coherent passage
within one accepted chunk. Repeated evidence belongs to the same occurrence
only while it supports the same uninterrupted activity. A kind change, combat
alignment change, intervening scene or meaningful absence, or transition from
mention to presence starts a new occurrence. Occurrences never span chunks, and
merge or normalization never semantically combines nearby, overlapping, or
cross-chunk records.
Normalization orders records by:
1. earliest valid source-document position;
2. the NPC identity comparison key;
3. the exact canonical NPC display name;
4. interaction kind in lexical order; and
5. the complete canonical source-reference sequence, ordered by source ID and
the source-document positions of each range's start and end.
Only records with identical canonical names, kinds, and complete valid evidence
sequences are duplicates. Different categories, ranges, or separately grounded
occurrences remain separate.
## Evidence, Registry, And Normalization
The registry proves only the canonical NPC identity. Its source references are
@@ -59,11 +93,8 @@ classification.
The extractor receives a names-only registry projection such as
`{"npcs":[{"name":"Mira Thorn"}]}`. The normalizer uses the full immutable
registry for exact canonical-name lookup. It orders source references,
stable-sorts occurrences by their earliest source-document position, and
collapses only exact duplicates with the same canonical name, kind, and complete
valid evidence. Different categories, distinct ranges, and separately grounded
occurrences remain separate; no semantic merge is performed.
registry for exact canonical-name lookup. It canonicalizes source references
and applies the ordering and exact-duplicate rules above.
## Production Pipeline
@@ -102,8 +133,10 @@ copying registry names, source ranges, or payload content into the manifest.
The default extract chain is `generic/valid_json`, interaction shape, registry,
and source-reference validation, `generic/valid_json_schema`, then warning-only
source relatedness. The normalize chain adds normalized invariants after schema
validation and before relatedness. The codec metadata contains only
source relatedness. The normalize chain runs normalized invariants after
registry validation and before source-reference and schema validation, followed
by relatedness. Normalizer and relatedness warnings are bounded and end with an
omission summary when necessary. The codec metadata contains only
`interaction_count`. Extractor metadata identifies its prompt and private
response schema; component-local checkpoint identities include the names-only
registry projection where relevant. Generated registry identity stays in

View File

@@ -299,10 +299,19 @@ evidence, identity, and transcript material, then maps private model records to
references are never reused as interaction evidence. The private response
schema carries only name, bounded interaction kind, and source-unit ranges;
deterministic validators own registry membership, source validity, and
relatedness. Prompt, schema, mapping, and the names-only registry projection
relatedness. Extract-stage source validation additionally requires every cited
range to be wholly contained in the current materialized chunk. Prompt, schema,
mapping, and the names-only registry projection
participate in checkpoint identity, while generated producer identity remains
framework provenance.
The domain-owned `internal/modules/dnd/npcinteractions` package defines
canonical source-reference and occurrence ordering, valid-evidence eligibility,
and collision-safe exact identity. The interaction normalizer and normalized
invariants validator both consume those rules, so their production and checking
paths cannot drift. Normalizer and relatedness warning lists use the shared D&D
diagnostic cap and emit a final omission-summary warning when truncated.
### `internal/modules/dnd/normalize/npcs`
The NPC normalizer performs deterministic identity-aware consolidation in

View File

@@ -97,6 +97,7 @@ Configuration. The implemented module packages are:
| `internal/modules/dnd/extract/npcs` | Maps private structured model output to canonical source-grounded D&D NPC lists. |
| `internal/modules/dnd/extract/combatturns` | Maps private structured model output to source-grounded D&D combat-turn candidates and preserves chronology and invalid candidate values for validators. |
| `internal/modules/dnd/extract/npcinteractions` | Maps private structured model output to current-source NPC interaction candidates grounded by a required registry. |
| `internal/modules/dnd/npcinteractions` | Owns canonical source-reference ordering, occurrence ordering, valid-evidence checks, and exact interaction identity shared by normalization and invariant validation. |
| `internal/modules/dnd/normalize/combatturns` | Canonicalizes and orders merged combat turns, applies exact NPC identity matches, and collapses only exact valid-evidence duplicates. |
| `internal/modules/dnd/normalize/npcinteractions` | Canonicalizes required-registry names, orders interaction occurrences, and collapses only exact valid-evidence duplicates. |
| `internal/modules/dnd/validate/combatturns` | Provides deterministic shape, source-reference, source-relatedness, and normalized-invariant validation for the production combat chains. |