338 lines
15 KiB
Markdown
338 lines
15 KiB
Markdown
# D&D Location Tracking
|
|
|
|
## Purpose
|
|
|
|
Add evidence-grounded D&D location tracking without turning a single extractor
|
|
into both an entity registry and an event classifier. The target design follows
|
|
the established NPC pattern: one lane identifies canonical location records and
|
|
a later lane records how the party related to those locations in the transcript.
|
|
|
|
This roadmap defines the desired end state and policy choices. The ordered work
|
|
needed to reach that state is in [the implementation plan](implementation.md).
|
|
|
|
## User Intent
|
|
|
|
- Record locations the party visits or that the session otherwise discusses.
|
|
- Distinguish current physical presence from plans, recollections, and ordinary
|
|
mentions.
|
|
- Preserve transcript evidence for every durable record.
|
|
- Reconcile aliases and repeated appearances conservatively.
|
|
- Keep distinct places separate when they happen to share a generic name.
|
|
- Keep the schemas minimal. Location description, hierarchy, participants, and
|
|
narrative analysis belong in other artifacts or deterministic joins.
|
|
|
|
## Target Capability
|
|
|
|
The D&D module family will have two new lanes:
|
|
|
|
1. `dnd/locations` produces a session-scoped registry of physical places.
|
|
2. `dnd/location-occurrences` consumes the normalized location registry and
|
|
produces an ordered list of source-grounded relationships between the party
|
|
and those places.
|
|
|
|
The normalized location artifact is handed to the occurrence lane through a
|
|
required generated reference named `locations`. The occurrence lane must use
|
|
that registry for identity grounding, but the current transcript remains its
|
|
only evidence source.
|
|
|
|
## Durable Artifact Contracts
|
|
|
|
Both contracts remain at `v1`; Notarius is pre-release and does not need a
|
|
compatibility layer for these new artifacts.
|
|
|
|
### Location registry
|
|
|
|
The location lane uses:
|
|
|
|
- artifact kind: `dnd/location-list`
|
|
- module key: `dnd/locations`
|
|
- schema ID: `notarius.dnd.locations`
|
|
- schema name: `notarius_dnd_locations_v1`
|
|
- media type: `application/json`
|
|
- root member: `locations`
|
|
|
|
Each location contains exactly:
|
|
|
|
| Field | Type | Meaning |
|
|
| --- | --- | --- |
|
|
| `id` | string | Deterministic, session-scoped canonical location identity. |
|
|
| `name` | string | Evidence-grounded display name or transcript-established label. |
|
|
| `source_refs` | non-empty source-reference array | Current-transcript evidence that identifies the place. |
|
|
|
|
Locations are physical or spatial places: planes, regions, settlements,
|
|
districts, buildings, rooms, landmarks, routes, and geographic features. A
|
|
generic label such as `the tavern` is permitted only when the transcript uses
|
|
it for a specific place. The extractor must not invent a qualifier merely to
|
|
distinguish that place from another place with the same label.
|
|
|
|
The registry does not contain type, parent, description, summary, coordinates,
|
|
participants, visit status, or occurrence data. Parent and child places are
|
|
separate identities when the transcript identifies both; nesting alone is not
|
|
a reason to merge them.
|
|
|
|
### Location occurrences
|
|
|
|
The occurrence lane uses:
|
|
|
|
- artifact kind: `dnd/location-occurrence-list`
|
|
- module key: `dnd/location-occurrences`
|
|
- schema ID: `notarius.dnd.location_occurrences`
|
|
- schema name: `notarius_dnd_location_occurrences_v1`
|
|
- media type: `application/json`
|
|
- root member: `occurrences`
|
|
|
|
Each occurrence contains exactly:
|
|
|
|
| Field | Type | Meaning |
|
|
| --- | --- | --- |
|
|
| `location_id` | string | An exact ID from the consumed normalized location registry. |
|
|
| `name` | string | The canonical display name associated with `location_id`. |
|
|
| `kind` | enum | `visited`, `planned`, `recalled`, or `mentioned`. |
|
|
| `source_refs` | non-empty source-reference array | Current-transcript evidence for both the place and the classified occurrence. |
|
|
|
|
`location_id` is required even though existing NPC interactions currently use
|
|
name-only grounding. Locations can legitimately share the same display name,
|
|
so a name alone cannot provide an unambiguous cross-artifact link. The name is
|
|
retained for readable standalone output and must exactly match the registry
|
|
record selected by the ID after normalization.
|
|
|
|
## Identity Policy
|
|
|
|
Location identity is conservative and scoped to one source document. It is not
|
|
a campaign-wide or cross-session world identity.
|
|
|
|
Display normalization trims surrounding whitespace and collapses internal
|
|
Unicode whitespace. Comparison normalization uses the existing D&D entity
|
|
rules: Unicode NFKC normalization, normalized apostrophes, collapsed
|
|
whitespace, and Unicode case folding.
|
|
|
|
The canonical ID is:
|
|
|
|
~~~text
|
|
location:sha256:<lowercase SHA-256 hex digest>
|
|
~~~
|
|
|
|
The digest input is the UTF-8 encoding of compact JSON for this five-element
|
|
array:
|
|
|
|
~~~text
|
|
["dnd.locations.identity.v1", comparison_name, source_id, start_unit_id, end_unit_id]
|
|
~~~
|
|
|
|
The source values come from the earliest reference after canonical reference
|
|
sorting and exact deduplication. Compact JSON array encoding is part of the
|
|
identity contract: it avoids delimiter ambiguity and must not be replaced
|
|
without changing the policy version. A blank comparison name or missing valid
|
|
source reference produces no manufactured ID and remains a validation error.
|
|
|
|
Including the evidence anchor prevents two unrelated places called `the
|
|
tavern` from receiving the same ID. When semantic normalization safely groups
|
|
aliases or repeated appearances, it first chooses an existing canonical display
|
|
name and unions the evidence; it then derives the final ID from that name and
|
|
the earliest unioned reference.
|
|
|
|
The normalizer may merge records only when transcript evidence clearly shows
|
|
that they denote the same physical place. It must not merge records solely
|
|
because:
|
|
|
|
- their comparison names are equal;
|
|
- they are near one another in the transcript;
|
|
- one is spatially nested inside the other; or
|
|
- their labels are both generic.
|
|
|
|
Distinct normalized records may therefore have the same comparison name, but
|
|
their IDs must be unique and correctly derived. Exact duplicates with the same
|
|
comparison name and canonical evidence may be collapsed deterministically.
|
|
|
|
## Occurrence Semantics
|
|
|
|
Each occurrence has one kind:
|
|
|
|
- `visited`: current-session gameplay establishes that one or more party
|
|
members are physically present at the location, including an arrival,
|
|
continuing presence, or departure.
|
|
- `planned`: the party explicitly proposes, intends, or agrees to future travel
|
|
to the location. Mere hypotheticals or speculation are not plans.
|
|
- `recalled`: the transcript explicitly recounts or recaps the party being at
|
|
the location before the current session's live events.
|
|
- `mentioned`: the location is explicitly referenced but the occurrence does
|
|
not meet a stronger definition. This includes lore, directions, third-party
|
|
activity, non-actionable speculation, and out-of-character discussion.
|
|
|
|
An inferred but unstated place produces no location or occurrence. Uncertainty
|
|
is handled by conservative omission rather than an `uncertain` enum value.
|
|
|
|
For one occurrence supported by overlapping evidence, classification precedence
|
|
is `visited`, then `planned`, then `recalled`, then `mentioned`; `mentioned` is
|
|
the fallback. A passage may produce multiple records when it independently
|
|
supports separate facts, such as recalling an earlier visit while planning a
|
|
return. Exact duplicates with the same ID, kind, and canonical evidence are
|
|
collapsed. Different kinds or independently supported evidence remain.
|
|
|
|
Output is ordered by earliest evidence in source-document order, then by
|
|
`location_id`, `name`, kind order (`visited`, `planned`, `recalled`,
|
|
`mentioned`), and the remaining canonical reference sequence.
|
|
|
|
## Extraction, Normalization, And Evidence
|
|
|
|
### Location registry lane
|
|
|
|
The extractor is LLM-backed and follows the shared D&D extraction prompt and
|
|
input conventions. It emits names and source ranges through a private response
|
|
schema; deterministic mapping supplies the current source ID and derives
|
|
candidate IDs. Campaign references may disambiguate terminology but never
|
|
become durable evidence.
|
|
|
|
The merger uses the typed append-order convention. The normalizer is LLM-backed:
|
|
it deterministically prepares names and evidence, then asks the model only for
|
|
duplicate groups. The model may identify groups and choose a canonical member,
|
|
but it may not create, delete, rewrite, or directly replace durable records.
|
|
Code validates the proposal, applies non-overlapping safe groups, unions
|
|
evidence, derives final IDs, orders output, and emits bounded warnings.
|
|
|
|
Malformed, unknown, overlapping, or ambiguous proposal groups are rejected.
|
|
The normalizer uses the existing bounded retry behavior and falls back to the
|
|
safe deterministic candidate set if no usable proposal is obtained.
|
|
|
|
### Location occurrence lane
|
|
|
|
The extractor is LLM-backed and requires exactly one validated `locations`
|
|
reference. The prompt projection contains only ordered `{id, name}` pairs; it
|
|
omits registry evidence and reference provenance. The model must copy both
|
|
values from one projected record and cite current-transcript source ranges for
|
|
the occurrence.
|
|
|
|
The occurrence normalizer is deterministic. It canonicalizes names by exact
|
|
registry ID, normalizes evidence and ordering, and removes exact duplicates.
|
|
Unknown IDs and mismatched ID/name pairs remain inspectable validation failures
|
|
rather than being guessed or silently reassigned.
|
|
|
|
The occurrence lane cannot add a missing location to the registry. If the
|
|
location extractor omitted a place, the correct behavior is to omit its
|
|
occurrence and improve the upstream extraction later.
|
|
|
|
## Shared Entity Reconciliation
|
|
|
|
Adding a second LLM-assisted entity registry demonstrates a concrete shared
|
|
need in the D&D domain. The existing NPC normalization context-window and
|
|
proposal-safety logic will move to
|
|
`internal/modules/dnd/shared/entityreconcile` and serve both NPC and location
|
|
normalizers.
|
|
|
|
The shared package owns:
|
|
|
|
- deterministic opaque candidate keys;
|
|
- source-window construction and canonical prompt materials;
|
|
- a common private duplicate-group response contract;
|
|
- validation of unknown, repeated, overlapping, malformed, or ineligible
|
|
candidate keys; and
|
|
- immutable assessment results identifying safe groups.
|
|
|
|
It does not call the LLM, choose domain-specific canonical names, derive
|
|
durable IDs, mutate domain artifacts, or format domain warnings. Those
|
|
responsibilities remain in each normalizer.
|
|
|
|
NPC normalization will migrate to the shared key-based proposal contract
|
|
without changing its durable NPC behavior. Its prompt and private response
|
|
schema fingerprints are expected to change, so stale NPC normalization
|
|
checkpoints will invalidate normally.
|
|
|
|
The two normalizers will reuse an exactly identical shared reconciliation
|
|
instruction asset and private response schema. Module-owned task text will
|
|
continue to define the different NPC and location identity rules. This keeps
|
|
shared prompt content identical without pretending the two domains have the
|
|
same semantic merge policy.
|
|
|
|
This helper is intentionally D&D-specific. It does not implement the broader
|
|
domain-neutral replacement-element normalizer still described in
|
|
[future work](future.md).
|
|
|
|
## Reference Contract And Pipeline Placement
|
|
|
|
The generated reference slot is named `locations` and accepts exactly one JSON
|
|
artifact of kind `dnd/location-list`, with the established 1 MiB limit. It is
|
|
required by both extraction and normalization for
|
|
`dnd/location-occurrences`. Static file bindings remain valid where the
|
|
framework permits them, but the maintained complete example uses a generated
|
|
same-run artifact.
|
|
|
|
The complete D&D pipeline places `locations` in the first descriptive step
|
|
alongside the independent NPC, item-event, and scene-description lanes. It
|
|
places `location-occurrences` in the next step and binds the accepted normalized
|
|
location artifact from the first step. The occurrence lane has no mandatory
|
|
NPC or scene-description dependency.
|
|
|
|
No current downstream lane is changed to consume location artifacts. Future
|
|
narrative reports or joins may use the canonical IDs after defining their own
|
|
contracts.
|
|
|
|
## Validation And Production Defaults
|
|
|
|
The location registry receives production validators for:
|
|
|
|
- required shape and supported ID syntax;
|
|
- current-document and current-chunk source ranges;
|
|
- normalized identity derivation and ID uniqueness; and
|
|
- advisory source relatedness.
|
|
|
|
The location occurrence artifact receives production validators for:
|
|
|
|
- required shape and the four supported kinds;
|
|
- registry membership and exact ID/name correspondence;
|
|
- normalized ordering and exact-duplicate invariants;
|
|
- current-document and current-chunk source ranges; and
|
|
- advisory source relatedness.
|
|
|
|
Validators remain immutable and diagnostic. Durable JSON Schema validation
|
|
stays in the production chains after semantic shape and source-reference
|
|
checks, consistent with the existing D&D lanes.
|
|
|
|
## Documentation End State
|
|
|
|
Implementation will add canonical integration documents for both durable
|
|
artifacts and update current-state documentation to cover:
|
|
|
|
- both module and artifact keys;
|
|
- the `locations` generated-reference slot;
|
|
- production validators and default chains;
|
|
- D&D family composition, reconciliation, identity, and grounding behavior;
|
|
- the complete maintained pipeline example; and
|
|
- JSON output and evidence-context support.
|
|
|
|
After the feature is implemented, the fulfilled Location Extraction section is
|
|
removed from `future.md`. Historical implementation narration remains in
|
|
version control rather than current documentation.
|
|
|
|
## Out Of Scope
|
|
|
|
- Campaign-wide or cross-session canonical location IDs.
|
|
- A location ontology, hierarchy, map, coordinates, or containment graph.
|
|
- Location descriptions, summaries, participants, ownership, or encounter
|
|
analysis.
|
|
- Inferring a location that the transcript does not identify.
|
|
- Automatically creating registry records from occurrence output.
|
|
- Changing NPC-interaction artifacts to use NPC IDs.
|
|
- Making other lanes consume location references.
|
|
- A generic domain-neutral LLM deduplication framework.
|
|
- Long-term artifact-version migration machinery.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- Both durable contracts are minimal, strict, versioned, and registered.
|
|
- Location IDs are deterministic under the documented policy and do not force
|
|
same-named places to collapse.
|
|
- Alias and repeat reconciliation is proposal-only, conservatively validated,
|
|
and safe on retry exhaustion.
|
|
- Occurrences use one of the four defined kinds and carry an unambiguous
|
|
registry ID/name pair plus current-transcript evidence.
|
|
- Missing, malformed, oversized, or incompatible `locations` references fail
|
|
through the established configuration or operation boundaries.
|
|
- Both lanes have typed mergers, normalizers, evidence projectors, validators,
|
|
default chains, prompt/profile metadata, and registration coverage consistent
|
|
with the D&D family.
|
|
- NPC normalization retains its durable behavior after adopting the shared
|
|
reconciliation helper.
|
|
- The maintained complete example loads and exercises the generated handoff.
|
|
- Focused D&D and integration tests pass offline, and current documentation
|
|
describes only implemented behavior once the work is complete.
|