Name resolution¶
Nerthus.Core (until cutover). This page describes the frozen system that runs today and is deleted at cutover. Replaced by: not yet written.
Polish-aware name resolution is unconditional core in Nerthus.Core. A session
mentions Erastera, Tussalowi, or Ithanowi; the resolver maps those inflected
forms back to the canonical entities Eraster, Lord Tussal, and Ithan. PU awards,
session distribution, and every @tag cross-reference depend on that mapping.
Polish is the only language — tags, types, and names are Polish literals in
canonical diacritical form (see Entities). The resolver runs in
the daemon against an in-memory name index, with the grammar compiled into C#
(Nerthus.DeclensionEngine, Nerthus.BKTree), plus one stage that reads the corpus's own
annotation. This page specifies the seven-stage resolver, the name index that backs it, and the -Type / -ActiveOn / -Within
disambiguation contract.
Concepts¶
- Token — a single indexed name string: a full name (
"Lord Tussal"), a registered@alias, a@slug, a@nazwa_nerthus, a@base_name, a@generyczne_nazwyvalue, or an individual word split out of a multi-word full name ("Lord","Tussal"). - Query — the surface form found in a session body or passed to
Resolve-NerthusName. It is frequently inflected:Erastera,Ithanowi,Tussalem. - Stem — the suffix-stripped form of a token (
Ithanowi→Ithan), used as the bucket key in the StemIndex so declined queries collapse onto their lemma (the dictionary base form). - Owner — the entity a token resolves to; resolution returns the owner, never the token.
- Homonym / Ambiguity — one token carried by several owners, e.g. the bare alias
Gildia Teologówshared by the Thuzal and Ithan chapters after an operator split (Locations); the owner filter disambiguates. - Declension — Polish case inflection, handled by plain suffix stripping (Stage 2), consonant-alternation reversal (Stage 2b), and — for the multi-word forms neither reaches — the annotated lemma (Stage 2d).
The name index (data model)¶
Built by the daemon from the entity model, held in memory only:
{
Index : Dictionary[string, IndexEntry] # OrdinalIgnoreCase — Stages 1, 2b, 3 keys
StemIndex : Dictionary[string, List[string]] # OrdinalIgnoreCase — Stage 2 buckets (stem → tokens)
BKTree : Nerthus.BKTree # fuzzy metric tree — Stage 3
Declension : Nerthus.DeclensionEngine # the grammar (see below)
Stats : { tokenCount, stemCount }
}
Each IndexEntry (all fields always populated):
| Field | Type | Meaning |
|---|---|---|
Source |
string | The token itself, as indexed |
Owners |
list | Every owner of this token, each a { Owner; Type; Priority } wrapper |
Primary |
wrapper | The collision-chosen default owner (see below) |
Ambiguous |
bool | True as soon as a second distinct owner claims the token |
Token sources & priority¶
Every entity contributes at priority 1: its canonical name plus every @alias,
@slug, @nazwa_nerthus, and @base_name value, and each comma-separated
@generyczne_nazwy value. At priority 2: each ≥3-character word of a multi-word
canonical name. So Lord Tussal indexes "Lord Tussal" (P1) plus "Lord" and
"Tussal" (P2). The owner's type is its @typ value (falling back to the ##
category heading of its index block).
Collision & the primary owner¶
A token the same entity claims twice deduplicates. A different entity claiming
an existing token joins Owners and marks the entry Ambiguous — regardless of
priority. Priority influences only which owner becomes Primary, the default pick
when no filter disambiguates:
- Lower priority number wins (a full name beats another entity's word token).
- At equal priority:
PostaćbeatsGracz,LokacjabeatsMapa. - Otherwise the first-seen owner stays primary (stable).
The Postać-beats-Gracz rule is why the separate Gracz and Postać entity types
(linked by @należy_do, see Players) coexist without a token
war: a shared name defaults to the in-fiction character, and -Type Gracz still
reaches the roster entry.
What feeds the index¶
Nerthus.MarkdownScanner (compiled C#, single-pass line scanner) is the one Nerthus
parser. It emits entity blocks (Category, Name, and a flat Tags list with raw
temporal values — operator prose is skipped) and raw session blocks (header +
verbatim body); the index builder consumes the entity blocks. There are no scan caches on disk — scanner output,
like the index, lives only in daemon memory (Architecture).
Build and rebuild¶
The index is built with the entity model and rebuilt whole whenever the daemon's
fingerprint self-heal detects a source change (Architecture).
POST /v1/api/name-index/rebuild forces the same full model rebuild on demand and
reports buildMs plus the index tokenCount / stemCount; use it to pre-warm
before a bulk session import.
Nerthus.DeclensionEngine¶
A compiled C# class (lib/, namespace Nerthus), constructed once at daemon start
from three parallel tables — the declension suffixes (longest-first), the alternation
inflected endings, and their base replacements. It exposes exactly two methods, both
EndsWith/Substring with OrdinalIgnoreCase:
| Method | Used by | Behavior |
|---|---|---|
GetStem(text) |
index build + Stage 2 | Strips the first matching inflection suffix (input order = longest-first); returns the input unchanged if none matches or the stem would fall below 3 characters (text.Length > suffix.Length + 2). |
GetAlternationCandidates(text) |
Stage 2b | Returns 0..N base-form candidates by reversing consonant mutations; the same minimum-stem guard applies; empty array if none apply. |
The suffix table, in engine order:
Bare -o is the Polish feminine vocative and also the nominative of a masculine name like
Mato, and it is stripped on both sides: the index stems its own tokens through this
table, so Mato buckets under Mat and the query Mata reaches it. Bare -e is not in
this table and must not enter it — the two are not symmetric, because -e is a common
nominative ending and stripping it would reduce whole names to their stems across every
consumer. Get-NerthusAliasStem states that rule at its own call site, and
Add-NerthusNormalizeConflicts keeps a separate 21-entry list which does carry -e,
because grouping two worklist rows under one stem cannot damage a lookup. The two lists are
pinned against each other in tests/Resolve.Tests.ps1.
The alternation pairs (inflected ending → base ending), in engine order:
Example: the locative Anwardzie triggers dzie→da and yields the candidate
Anwarda — a candidate is accepted only on an actual index hit (Stage 2b below).
-ście appears twice — both readings (-sta, -ść) are emitted as candidates.
The pipeline (behavior)¶
Resolve-NerthusName runs up to seven stages in order; each stage funnels its candidate
token through the same -Type / -ActiveOn / -Within owner filter (below), and the first
stage whose filtered candidate survives wins:
Query "Anwardzie"
│
├─ Stage 1 Exact O(1) dictionary lookup ........... miss
├─ Stage 1b Computed alias (the `exact` tier) ...... miss (no proven row for this surface)
├─ Stage 2 Declension suffix-strip → StemIndex .... miss (stem "Anwardz" hits no bucket)
├─ Stage 2b Consonant-alternation reversal ......... miss (-dzie → -da → "Anwarda", not indexed)
├─ Stage 2c Title strip ............................ miss (no role word in front)
├─ Stage 2d Annotated lemma (names.tsv) ............ miss (no row for this surface)
└─ Stage 3 Levenshtein fuzzy (BKTree) ............. HIT distance 3 → "Anward"
→ Owner: Anward (Gracz)
Each result records the stage that matched (exact / computed / declension /
alternation / title / lemma / fuzzy) and a confidence score: 1.0, 0.97, 0.95,
0.9, 0.88, 0.92, or 1 − distance/|query| respectively — so an operator can see why
Anwardzie resolved to Anward.
Stage 1 — Exact lookup (O(1))¶
Case-insensitive dictionary hit against Index. Catches canonical names, aliases,
slugs, and @nazwa_nerthus values verbatim: "Ithan", "Lord Tussal",
"rezydencja-tussal" all land here.
Stage 1b — Computed alias (O(1))¶
A form the alias pass proved injective on the corpus — it maps to one block and provably
cannot map to another — looked up in the store the daemon publishes beside its other caches and
resolved as the block it names. Frycek → Fredrick Flumiene lands here when no @alias records
it. The tier that produces those rows is specified with
the normalization worklist. Since ruling H124.1 a row this tier
proves exact with no rival does not stay here for long: the alias autopilot
writes it as an (auto) alias line within half an hour, and the next index build answers it at
Stage 1.
It sits after Stage 1, permanently. A surface the lore itself names must never be
reinterpreted by a computation: a hand-written @alias is an operator's decision and this is a
machine's. It sits before declension because it is an equality on a form no suffix rule reaches,
and 0.97 says exactly that — below a name the lore wrote, above a name grammar reconstructed.
Three things bound it. Global rows only: a session-scoped row is injective only given a cast and a resolver call carries none, so those are dropped when the store is read. Exact on both legs: the query is matched by fold and the row's block is resolved as written, with no stemming or fuzzy on either side. And the store must describe this corpus — it carries the stamp it was built from, covering the archive, the entity model and the annotation digest alike, and a file whose stamp does not match is ignored entirely rather than read. A host with no store, or with one describing another corpus, resolves exactly as it did before this stage existed.
The stamp is checked on every read rather than captured once, because the corpus moves without this index being rebuilt: fetching a transcript moves the archive segment while the entity model sits still. Nothing republishes the store on a boot or a tick — a full-surface worklist read is its only writer — so a stale file persists until such a read notices, and restarting the daemon does not clear it.
Stage 2 — Declension suffix-strip → StemIndex (O(1))¶
For queries of ≥3 characters, GetStem(query) strips the first matching suffix and the
result is looked up in the StemIndex, whose buckets were filled at index time by
stemming every token once. Suffixes are tried longest-first so -ami is removed before
-i; the 3-character minimum stem keeps short names intact.
Examples: Ithanowi → Ithan (bucket Ithan holds Ithan), Rezydencji → Rezydencj (bucket
holds Rezydencja), Perrinie → Perrin (-ie stripped; bucket holds Perrin),
query Tussalem → Tussal (bucket holds Tussal). Both sides must stem to the same
key. A masculine -o lemma like Losso stems to itself (-o is not a listed
suffix), so the instrumental Lossem → Loss misses the Losso bucket and
falls through; Lossem actually resolves at Stage 3, distance 2 (a recorded
design decision — see below).
Stage 2b — Consonant-alternation reversal (O(1) per candidate)¶
When a Polish suffix mutates the stem-final consonant, plain stripping cannot recover
the lemma. GetAlternationCandidates(query) strips a known inflected ending and
re-appends the corresponding base ending (table above), producing candidate lemmas that
are each looked up directly in Index. A candidate may be wrong — the reversal is
heuristic — so only an actual index hit accepts it; a miss falls through harmlessly.
Anwardzie illustrates both halves: -dzie → -da produces Anwarda, which is not indexed,
so the pipeline continues and Stage 3 finds Anward at distance 3.
Stage 2c — Title strip (O(1))¶
A role in front of a name is not part of the name, and the transcripts are full of both:
Kapitan Bejkadus, Sir Layonel Lodowa Łuska. The stage strips a known title and looks the
remainder up exactly. It has to change the string and the result has to land exactly, so a form
carrying no title leaves untouched.
The hit is held to a person type (NPC, Postać, Gracz). A title says what the referent is,
and Ambasador Elancji strips to a token this corpus indexes for an embassy building — without the
type guard the stage answered a person's line with a house.
Stage 2d — Annotated lemma (O(1))¶
The corpus's own annotation, read from nerthus.lang/names.tsv — folded surface → lemma, label,
attestations — published by Nerthus.Lang
from the entity spans it writes. The query is folded, looked up, and the lemma is resolved as
written.
This is the stage that reaches what a suffix table structurally cannot: multi-word declined
forms. Fortu Eder → fort Eder, Doliny Yss → dolina Yss, Domu Schadzek → dom Schadzek.
Measured on the development corpus, of 989 annotated surfaces whose lemma is a canonical entity
name, 168 are reachable by none of the stages above and 112 of those are multi-word.
Three rules govern it:
- After Stage 1, permanently. A surface the lore itself names is never reinterpreted by the annotation. Nerthus.Lang holds the same rule on the writing side and publishes no row for such a surface; Stage 1 running first is how this side holds it regardless.
- Exact only. The lemma is resolved as written — no stemming it, no alternation on it, no fuzzy
through it. A lemma that lands on nothing falls through. The tagger disagrees with the stemmer on
25 % of labelled surfaces and most of that is noise (
M → metr), which is what a looser rule would import into resolution. - The label is not a filter, and the reason for that is now smaller than it was. It records what the tagger read, not what the lore holds, and it labelled NPCs as places often enough that filtering on it would lose them — 17.0 % of the spans landing exactly on an indexed name carried a contradicting family, 5 033 of them a place read as a person. Nerthus.Lang's gazetteer takes that to 6 rows on the same slice, because an indexed name is now labelled by the index rather than guessed. The rule stands anyway until a tree annotated under the gazetteer has been measured through this stage, and it stands permanently for surfaces the index does not carry, which is where the label is still a guess.
A repository with no annotation tree has no Stage 2d and resolves exactly as it did before the layer existed. That degradation is the contract for the whole annotation tree, and it is asserted in the suite rather than assumed.
Stage 3 — Levenshtein fuzzy (BKTree)¶
The last resort, for typos (Erastr → Eraster). The index holds a
Nerthus.BKTree metric tree over all tokens; Search(query, threshold) prunes
subtrees that cannot contain a within-threshold match, giving effective O(log N)
lookup over the few-thousand-token index. Distance is two-row, case-insensitive
Levenshtein. The threshold is dynamic and length-based, overridable with
-MaxDistance:
Matches are sorted by distance, then name; each is funneled through the owner filter
and the first survivor is the answer. -TopN caps how many alternatives the daemon
collects internally, but fuzzy near-misses are not part of the wire response —
the response's candidate list carries homonym owners of the matched token.
Structured-value routing always disables fuzzy
This is the rule's normative home: PU character names, @Transfer endpoints,
distribution participants, Discord delivery targets, and game-log
segment/speaker resolution all resolve with -NoFuzzy — a wrong guess would
silently misroute value. Declension and alternation still apply; only the
edit-distance stage is off, so an unmatched name returns $null instead of a
near-miss. There is no caching of misses.
A query that survives no stage is a hard miss; the PU monthly batch treats any unresolved character name as fatal and aborts with no partial writes (PU model).
The owner filter — -Type, -ActiveOn, -Within¶
Every stage's candidate token passes through one owner-selection filter:
-Typekeeps only owners of the requested type; aLokacjafilter also admitsMapaowners (map-as-location). When both a trueLokacjaand aMapasurvive, the exact requested type is preferred.-ActiveOn <date>(default: now) excludes owners whose@statusat that date isUsunięty— owners with any other status, or no@statusat all, still resolve. A currently soft-deleted entity therefore never resolves by default, but a historical-ActiveOnpredating the deletion still reaches it (temporal value scopes: tag schema).- If multiple owners survive, the resolver picks one — it never returns
$nullfor ambiguity: -Within <city>wins first. The filter picks the owner whose active@outerioror@lokacja(the computed chain root and the parent location) at-ActiveOnnormalizes to the given city. Failing that, it accepts an owner whose@forma_sesyjna(a literal session route form) references that city in the exterior segment before the leaf or the first path segment. This catches a guild canonically seated in Port Tuzmer but writtenTuzmer/…in sessions. Name normalization is the deterministic folding owned by Locations.- Temporal preference next: owners carrying
@lokacja/@outeriortags with no value active at-ActiveOn(a guild not yet founded) are dropped from the pool. Owners with no containment tags at all are always kept, so Postać/Gracz collisions are never disturbed. - Then the entry's
Primary, then the first survivor. - Whatever was picked, the result reports
Ambiguous = truewhenever more than one owner survived filtering, plusOwnerCandidates— the surviving owner names — so a caller can see that a-Withinor-Typehint would make the pick deliberate.
Zero survivors make the stage miss (the pipeline continues); zero survivors at every
stage yield $null.
The cast preference — -Cast¶
A form written inside a session is written in a room, and the room is evidence the stages
above cannot see. Riv is a word of the heading Riv Devisson, so the index answers with
him for all 1 565 of the archive's spans of it — and 1 407 of those sit in sessions whose
cast holds Riveth and not Riv Devisson. Nobody wrote a global @alias: Riv, and nobody
should: it is his name too.
-Cast <block names> is how a caller that knows the session says so. It runs after the
pipeline, it never changes which stage answered, and it can only ever move an answer the
index already gave. A form the stages answer nothing for stays unanswered — «Midaven»
is a word of Riveth's alias, which the index does not split, and in the archive's
2024-06-30 it names Daelin on eleven lines and Riveth on two, so a tier that answered
there would be wrong more often than right.
Three conditions, then two ways a cast member can carry the form:
- The index answered.
- The block it answered with is not in the cast. When it is, the answer stands —
this is
Rivin the 2024-11-15 session whose cast holds Riv Devisson. - Either the token that answered has a cast member among its own owners —
Aureliais a heading and the first word ofAurelia Lesup, and only the priority rule chose between them — or a cast member's own name or alias word begins with the queried form, and then the hit must be a fragment: priority 2, or a stage that already approximated. A priority-1 hit on a whole canonical name is the lore naming something outright and no room overrides it.
The result carries Stage = 'cast' and Score = 0.99. Two carriers and neither the
index's answer is not a tie-break: both are returned in OwnerCandidates with
Ambiguous = true, the shape a shared epithet already uses.
Measured over the 1 464 annotated transcripts before the tier was written
(126-ner-next/scripts/84-cast-tier-population.ps1): 79 (form, block) pairs move,
7 203 spans. The largest classes are a player's own name reaching their character
(Brimm → Brimm Schadenfreude, 3 065 spans; Fredrick → Fredrick Flumiene),
Riv → Riveth (1 389), and a family name the room decides (Dragonius,
Schadenfreude, Vruzael).
Who passes a cast: the Kto to? read (Get-NerthusSessionBindingsService), the speech
index's per-transcript speaker resolution, and the mention read behind
GET /entities/{name}/mentions, which drops a hit whose token the room hands to somebody
else. Who does not, and why: the corpus-wide hygiene sweeps have no session by
construction, and the archive parse writes a speaker map into the sidecar, where a cast
captured at write time would outlive the session record it came from.
-Within and -ActiveOn compose. With Gildia Teologów in Ithan since 2023-01
and a new chapter in Thuzal since 2025-03, -Within Ithan -ActiveOn 2024-01-15
reaches the Ithan chapter and -Within Thuzal -ActiveOn 2025-06-01 the Thuzal one.
Without -Within, in 2024 the temporal preference alone picks Ithan, while in
2026 both exist and the pick is flagged Ambiguous.
Cmdlet surface¶
Resolve-NerthusNameService— the daemon-side resolver. Takes-Cast(above) in addition to the filters; a caller with no session passes none and gets exactly the answer it got before the tier existed.Resolve-NerthusName— the resolver. One name resolves singly; several names in-Nameswitch to batch resolution. Batch supports only-Type,-ActiveOn, and-NoFuzzy;-Within,-TopN, and-MaxDistanceapply to single-name resolution.-Typeis a closed set —NPC,Grupa,Lokacja,Mapa,Gracz,Postać,Przedmiot— so an off-list value is a bind error, not a silent miss.-MaxDistancedefaults to-1(fall back to the length-based Stage 3 threshold; any value ≥0 overrides it), and-TopNto1.
Routes, request body fields, response envelopes, capabilities, and the diagnostic
GET /v1/api/name-index/lookup/{token} and POST /v1/api/name-index/rebuild
(no cmdlet wrappers) are specified in the API reference.
Examples (real Nerthus data)¶
# Consonant alternation: -dzie reversed to -da; the candidate misses, fuzzy completes
Resolve-NerthusName 'Anwardzie'
# stage: fuzzy → Anward (Gracz), confidence 0.667
# Plain declension suffix strip: -owi
Resolve-NerthusName 'Ithanowi' -Type Lokacja
# stage: declension → Ithan (Lokacja), confidence 0.95
# An -o lemma: the stems don't align, so fuzzy catches it at distance 2
Resolve-NerthusName 'Lossem'
# stage: fuzzy → Losso Minewit (Postać), confidence 0.667
# Homonym + era: three filters composing
Resolve-NerthusName 'Gildia Teologów' -Type Grupa -Within 'Ithan' -ActiveOn 2024-01-15
# stage: exact → Gildia Teologów (Ithan)
# ambiguous: true; candidates: Gildia Teologów (Ithan), Gildia Teologów (Thuzal)
# Strict resolution for value routing — never guess
Resolve-NerthusName 'Ithn' -NoFuzzy
# → $null (exact + declension only; no fuzzy false positive)
# Batch resolution during session distribution
Resolve-NerthusName -Name 'Erastera','Tussalowi','Opat Perrin' -ActiveOn 2026-07-01
# → one { token, resolved, typ, stage } item per query; stage 'miss' on failure
# Diagnose an ambiguous token without touching daemon internals
curl -s 127.0.0.1:$PORT/v1/api/name-index/lookup/Eraster \
-H "Authorization: Bearer $TOK"
# → { "source":"Eraster", "ambiguous":true,
# "owners":[ {"name":"Eraster","type":"Gracz","priority":1}, {"name":"Eraster","type":"Postać","priority":1} ] }
Quantities: how many, of what¶
A session read answers who. A line like RNdb9gG3:101 — «Straż floty w liczbie pięćdziesięciu. Trzech gońców. Kwatermistrza. Dwa tuziny obsługi» — states four whats, and until the quantity recogniser landed the read answered one row for the whole session and nothing about the guard, the fleet or the fifty.
Two implementations of one grammar, on purpose. nerthus_lifecycle.quantity in Nerthus.Lang is
the instrument: it runs beside the parse, over Morfeusz's readings, and its answer travels in the
sidecar. Get-NerthusQuantity (daemon/services/Quantity.ps1) is the fallback, for a line whose
log has no sidecar, and the cross-check that says the two agree. Where the sidecar carries a
quantity the daemon reads it and does not compute its own. Both ship the same
data/quantities.json; the numbers in it are the operator's (ruling H126.4, 2026-09-06), and a
disagreement between the two readers on an archived log is a listed defect rather than a tie-break.
What the table covers. Digits; inflected cardinals including compounds («stu pięćdziesięciu» is 150, not 100); collective numerals («pięcioro»); collective nouns («trójka»); idioms («tuzin» 12, «kopa» 60, «mendel» 15, «Dwa tuziny» 24, «pół setki» 50); vague quantifiers with published bounds («kilka» 3–9, «kilkanaście» 11–19, «kilkadziesiąt» 20–99, «kilkaset» 200–999); unbounded words («garść», «tłum») with a low bound and no ceiling; approximators («około», «ze», «ponad», «do»); ranges («dwudziestu, trzydziestu», «pięciu albo sześciu»); and distributives («po dwie»).
The head rule is the precision guard. A count attaches only to an ELIGIBLE head — a lexicon
role, creature or group, or a supplement head the lexicon lacks (ludzie, osoba, obsługa,
flota, karawana, konwój) — and only inside its own clause. Three consequences a reader should
be able to predict:
- A numeral with no eligible head in its clause counts nobody. «Pięćdziesięciu na granicę, osiemdziesiąt do miasta, siedemdziesiąt do Bramy Północy» yields nothing, and the recogniser must not reach back to the line before for a head.
- An excluded head STOPS the scan rather than being skipped. «dodatkowe dziesięć wozów na całe
wyposażenie floty» is ten wagons; a reader that treats
wozówas noise walks on and reports a fleet of ten. - Money, time, distance and items are excluded by name. «piętnastu talarów» and «dwa tuziny strzał»
are silent, which is why
talarandsrebrnikleaverolesat the next format bump.
Scope. A head may carry a scope: a genitive complement right after it («straż floty», «strażnik zakonu»), a classifying adjective on either side («elfia straż», «straż pożarna», «straż miejska» — Polish puts one after the noun as readily as before it), or the clause's own subject when that subject names a place, an organisation or a unit («miasto powinno mieć ze stu pięćdziesięciu strażników»). A subject that is a PERSON is never read as a scope: a unit whose scope is a cast member is that member's, never that member, and binding fifty guards to whoever spoke last is the failure the whole unit path exists to avoid.
Modality and runs. A count is not a fact until something says how it was asserted. An unsigned
line is narrator prose and the fiction's ground truth; a signed line is a character asserting
something inside the fiction, so asserted_by is a field on every reading rather than a footnote.
A line with no cue of its own takes the nearest cue in its run — H126.6's three-minute window —
which is how «Potrzebujemy» on RNdb9gG3:100 makes line 101 a plan rather than a report.
Why it works this way¶
- Adjectival multi-word inflection. Both words of
Stary Werbindecline independently, so each word ≥3 chars is indexed at priority 2 and resolved per-token; whole-phrase declension is not modeled. - Alternation before fuzzy. Stage 2b runs ahead of Stage 3 — a validated grammar reversal beats an edit-distance guess.
- Masculine
-olemmas.Losso-class inflections (Lossem) resolve at Stage 3 rather than Stage 2 (mechanics under the pipeline above). - ASCII legacy reads. Diacritic-stripped forms are read on import only; the live index holds diacritical canon (adoption).
See also¶
- Architecture — the daemon-as-data-owner contract, the fingerprint
self-heal, the
Nerthus.*C# substrate - API reference — routes, request/response envelopes, capabilities
- Entities model and the tag schema —
token sources (
@alias,@slug,@nazwa_nerthus,@base_name,@generyczne_nazwy),@status, temporal value scopes - Sessions model — mention extraction and distribution that consume the resolver
- Players model — the Gracz/Postać split behind the primary-owner rule
- PU model — fail-early on any unresolved character name
- Locations model — homonym splits, the deterministic name
normalization behind
-Within - Entity model — the
@forma_sesyjnaclaim ledger and its(auto)markers, beyond the token sources above