# 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.
