The location and map model¶
The locations subsystem turns the hand-authored Polish geography of
repozytorium-fabularne (Świat gry/Werbin, Świat gry/Eder, Świat gry/Ithan, …)
into two queryable structures. One is a hierarchical location tree: every
Lokacja knows its parent via the @lokacja chain. The other is a multi-source
connectivity graph: where you can actually walk, fly, or teleport between places.
It also models the Margonem map layer — the Mapa entity type, keyed by the
in-game @margonemid, projected back down onto the lore Lokacja it depicts.
Session mentions land in the tree through a claim ledger the index itself
carries — routing is steered by editing the file. The daemon owns all of this: the
client only ever asks for an already-assembled tree or graph. For the plain-language
introduction, start at Locations.
Concepts¶
- Lokacja — an in-fiction place: a town (
Thuzal), a district (Werbin/Miasto), a building (Rezydencja Tussal), a room. One of the seven entity types (see the entity model). - Mapa — a Margonem game map (an image on a grid of 32×32-pixel tiles) that
depicts a Lokacja. Identified by its in-game numeric
@margonemid(e.g. Werbin is map9, Eder33, Ithan1). A Lokacja can be depicted by several Mapy (floors, event-copy Kopia instances); a Mapa depicts exactly one Lokacja. - World root / exterior — a top-level
Świat gry/directory (Ithan,Eder,Werbin,Tuzmer, …): the cities and region containers the world map is made of. Everything else is an interior — contained, directly or transitively, in a root. A Mapa may additionally carry the authored marker@typ_lokacji: zewnętrzna/wewnętrzna(exterior/interior game map). - Outerior — the daemon's computed world root for a place: walking the
@lokacjacontainment chain upward until a top-levelŚwiat gry/root is reached.Rezydencja Tussal's Outerior isThuzal. - Claim (roszczenie) — a
- @forma_sesyjna: <forma>line on anerthus.entities.mdblock. It claims that literal session form for the block: the normalizer routes every mention of the form there. Together the claim lines are the mention-routing plan — the plan IS the index, versioned with it in git. - Traversal graph — the merged "what connects to what" graph, assembled from entity tags, session metadata, and parsed game logs.
Polish is core here as everywhere: the tags, the @typ_lokacji values
(zewnętrzna/wewnętrzna), the claim-lint findings (konflikt-roszczeń,
roszczenie-bez-wzmianki), and the Margonem map-name suffixes the stripper
understands (p.2, sala 1, piwnica, poziom: trudny) are Polish literals.
Data model — the Lokacja entity¶
A Lokacja is an entity block carrying the location tags. Hand-authored geography
(the Świat gry/ directory tree) and session-derived interiors both land in the
single generated nerthus.entities.md. Its ## Lokacja section union-merges the
two sources into one block per name; the ## Mapa registry is the file's last
section. The section is hand-editable: the importer merges per block, re-derives
only its machine-owned tags, and preserves operator lines verbatim (the merge
contract belongs to the entity model). The importer never
rewrites the hand-authored Świat gry/**.md prose. Relevant tags:
| Tag | Meaning | Example |
|---|---|---|
@typ |
The entity type — always Lokacja |
@typ: Lokacja |
@lokacja |
Parent location — the containment chain | @lokacja: Thuzal |
@outerior |
Computed world root (never authored by hand) | @outerior: Thuzal (2026-07:) |
@drzwi |
Door/passage to another Lokacja — authored, or log-derived with (auto) (see Doors) |
@drzwi: Thuzal |
@koordynaty |
Tile position X, Y on the world map / parent Mapa grid |
@koordynaty: 125, 80 |
@alias |
Alternate names — indexed by the resolver | @alias: Gildia Teologów |
@forma_sesyjna |
A claim: routes every mention of this literal form to this block; (auto) = machine-appended |
@forma_sesyjna: Thuzal/Rezydencja Tussal (auto) |
@status |
Aktywny / Nieaktywny / Usunięty; Niepewny marks quarantine |
@status: Aktywny |
@plik |
Authoring location (the Świat gry/ directory) |
@plik: /Świat gry/Thuzal/Rezydencja Tussal/ |
The full closed tag schema, the link-value format, and the temporal range grammar
live in the tag schema reference. The retired
projection tags @mention_count, @scalono, and @nadpisanie are stripped at
merge — attestation counts and merge lineage live in git history now. @duplikat
(duplicate-source provenance, owned by the entity model)
stays.
Hierarchical canonical name (the @lokacja chain)¶
A location's canonical name is its hierarchical path: each Lokacja's name,
prefixed by its parent's, parent's parent's, and so on up the @lokacja chain
until a world root. This is what makes a bare room name unambiguous repo-wide:
Rezydencja Tussal
@lokacja: Thuzal
(Thuzal is a top-level Świat gry/ directory → world root)
⇒ canonical path: Thuzal/Rezydencja Tussal
A cycle in the @lokacja chain is a hard integrity Error
(Test-NerthusEntityIntegrity); the chain must terminate at a world root.
@koordynaty — the tile position¶
@koordynaty is X, Y, two integer tile coordinates (each tile is 32×32 px).
It is authored geometry; exterior status comes from world-root
membership. Like every value it is temporal
(see the tag schema reference) — a town that moved
on the map carries its history with per-value date ranges:
Exterior enrichment — @outerior (implemented), @isexterior/@inheritsfrom (deferred)¶
For every session-derived Lokacja the importer walks the containment chain (the
parent edges known to the ground-truth sources, below) up to a world root and
records the result as @outerior. That tag is the exterior a place resolves
to, the disambiguator -Within <city> reads, and the anchor for homonym splits.
It is computed enrichment, never an authored tag.
Two further enrichments are deferred; their tags are reserved in the closed
schema. @isexterior would be a three-state classification from active
@koordynaty and depicting-Mapa @typ_lokacji evidence. @inheritsfrom
would let an interior inherit its effective world-map position from its Outerior
(a room is "at" its town's coordinates). Revisit both with the map checkup below.
Data model — the Mapa entity¶
A Mapa carries the Margonem game-map identity and links to the Lokacja it
depicts via @lokacja:
| Tag | Meaning | Example |
|---|---|---|
@typ |
The entity type — always Mapa |
@typ: Mapa |
@margonemid |
In-game map id (the registry join key) | @margonemid: 4120 |
@url |
Source Margonem image URL | @url: https://micc.garmory-cdn.cloud/obrazki/npc/rezydencja_tussal.0.png |
@url_nerthus |
Nerthus-hosted mirror of the image | @url_nerthus: https://nerthus.pl/mapy/rezydencja_tussal.png |
@url_grafika |
Alternate image (labelled, repeatable) | @url_grafika: https://i.imgur.com/QVi9jT2.png "Mapa poglądowa" |
@slug |
Stable Margonem image slug (from the URL) | @slug: rezydencja_tussal |
@wymiary |
Map dimensions in tiles W×H |
@wymiary: 96 x 96 |
@koordynaty |
Map's anchor tile on the world map | @koordynaty: 125, 80 |
@typ_lokacji |
zewnętrzna / wewnętrzna (exterior/interior map) |
@typ_lokacji: wewnętrzna |
@alias |
The stripped base name (when it differs) | @alias: Rezydencja Tussal |
@lokacja |
The Lokacja this map depicts | @lokacja: Rezydencja Tussal |
@drzwi |
Passage to another Mapa, log-derived with (auto) (see Doors) |
@drzwi: Thuzal (auto) |
The importer emits @margonemid, @url, @slug, @alias, the @lokacja
projection, and the log-derived @drzwi (auto) doors; @wymiary, @koordynaty,
@typ_lokacji, @url_nerthus, and @url_grafika are the operator write
surface (New-NerthusMapEntity / Set-NerthusMapEntity). A note that fits no tag is
written as prose in the block (entity model). The link-value format
(URL, optional "label", optional range) is specified in the
tag schema reference. @margonemid is the
registry's join key: the bootstrap snapshot ingest keeps the last entry per id,
and the index read-back resolves a duplicate id last-loaded-wins.
The Mapa → Lokacja projection¶
The relation is many-to-one: a Mapa depicts exactly one Lokacja, and a Lokacja may
be depicted by any number of Mapy. Margonem draws the world in map-sized pieces, and
a lore place is routinely bigger than one piece — every floor of Rezydencja Tussal is
its own map with its own @margonemid, and all of them are the one residence. Event
copies of a city and floor-by-floor dungeons are the same shape. Nothing but the
game's rendering identity lives on a Mapa: sessions, PU, transfers, containment, and
the location tree all attach to the Lokacja, and the tree reads Lokacja blocks
only, so a Mapa is never a node in the world hierarchy.
Every Mapa resolves through its @lokacja to exactly one Lokacja. This is
what lets the daemon answer "which lore place is the player standing in?" from a
raw Margonem map name in a game log, and what lets the traversal graph collapse
map-level movement to location-level movement — the collapse is the many-to-one
relation doing its job. The projection is a single exact (case-insensitive) match:
if the map's stripped base name is one of the authored Świat gry/ location names, the
map gets @lokacja: <base name>. That base name is what fans many maps into one place:
interior maps with distinct ids (Rezydencja Tussal p.1 id 4120,
Rezydencja Tussal p.2 id 4121) both strip to Rezydencja Tussal, so both project
onto that Lokacja and both carry it as their base-name @alias; the resolver then
finds them all under the building's name, and a -Type Lokacja query also admits Mapa
owners (see Name resolution). An unmatched map carries no
@lokacja until an operator sets it (Set-NerthusMapEntity).
The map registry and the name stripper¶
Margonem exposes the authoritative list of map ids via the game's map registry.
Once the index carries a ## Mapa section, that section is that registry:
every index rebuild reads it back via Read-NerthusIndexMaps. Only Mapa blocks
carrying @margonemid and a currently covering @url join — the id is the
registry identity and the URL its render, so a block missing either is not
registry material (the fixpoint merge preserves it as disk-only state instead).
Soft-deleted (Usunięty) blocks stay out the same way. Each record's URL is the
block's current @url — the covering value under temporal resolution,
checkup-fresh where the legacy snapshot had rotted — and a duplicate id resolves
last-loaded-wins, mirroring the legacy reader's last-occurrence rule.
When the index has no ## Mapa section yet, the registry bootstraps from the
legacy repo's cached snapshot .robot/res/maps.md instead:
## Mapy
- 2024-03-21 20:40 (UTC+01:00):
- Id: 9; Nazwa: Werbin, Url: https://micc.garmory-cdn.cloud/obrazki/miasta/werbin.10.png
- Id: 33; Nazwa: Eder, Url: https://micc.garmory-cdn.cloud/obrazki/miasta/eder.7.png
- Id: 1; Nazwa: Ithan, Url: https://micc.garmory-cdn.cloud/obrazki/miasta/ithan.8.png
- Id: 766; Nazwa: Ratusz Ithan p.1, Url: .../ratusz-ithan-p.1.png
- Id: 4120; Nazwa: Rezydencja Tussal p.1, Url: .../rezydencja_tussal.0.png
The importer ingests each entry — line shape - Id: N; Nazwa: <name>, Url: <url>
(semicolon after the id, comma before the URL) — into a Mapa entity
(@margonemid: N, @url: <url>, name from Nazwa, @slug from the image
filename).
Once the index carries ## Mapa, that section is the sole registry and the
bootstrap snapshot is no longer read; retiring the still-present
.robot/res/maps.md is the operator's own commit — the same pattern as the
retired Gracze.md roster seed (see the roster model).
A new map enters the registry via entity creation: a Mapa block carrying
@margonemid + @url (New-NerthusMapEntity, or a hand-authored block), until
a live-registry diff ships (deferred — see the checkup below).
To derive the base name a raw Margonem name projects from, the importer runs the
Polish/Margonem name stripper (Get-NerthusStrippedMapName), an iterative
pass that re-applies all patterns until the name is stable — so stacked suffixes
strip fully:
| Pattern stripped | Example |
|---|---|
Difficulty (poziom: <wartość>) — any value, anywhere in the name |
Lezysko Baraniego Kanoniera (poziom: trudny) → Lezysko Baraniego Kanoniera |
Trailing floor p.N |
Rezydencja Tussal p.2 → Rezydencja Tussal |
Trailing sub-area s.N / - sala N / - Sala … |
Kopalnia Bandytów p.2 - sala 1 → Kopalnia Bandytów p.2 → Kopalnia Bandytów |
Trailing piwnica |
Karczma - piwnica → Karczma |
Trailing cardinal after a hyphen (- wsch., - zach., - płn., - płd., - północ, …) |
Las - wsch. → Las |
Kopia <name> - <event> instance wrapper |
Kopia Ithan - Urodziny 2023 → Ithan |
Trailing separators -, --, --- |
Chata bandytów - → Chata bandytów |
These are proper Margonem nouns, not inflected Polish prose, so declension/fuzzy matching is deliberately not applied here (that belongs to Name resolution) — only literal suffix stripping, to avoid false positives between similarly named maps.
The deterministic location normalizer¶
The importer's location stage emits the enrichment into the ## Lokacja section
of nerthus.entities.md, derived from the interiors named in session @Lokacje
(see the session model). A place already skeletoned from
the Świat gry/ directory (a dateless @lokacja parent plus the @plik
directory path) receives the enrichment in that same block: the temporal
(YYYY-MM:) @lokacja/@outerior lines, @alias resolver tokens, and
machine-marked @forma_sesyjna claim lines. Enrichment-only places and
quarantined (@status: Niepewny) blocks follow in the same section.
Only mentions with an exterior prefix feed the enrichment — a bare top-level mention is already a Świat gry location or a Mapa. Because the session parser records movement as raw strings, that set is noisy. It holds:
- concatenated traversal paths (
Mury/kwatera główna/Mury/zbrojownia); - floor/room subdivisions (
Cytadela p.1,Pokój 202); - spelling/case/declension variants of one place
(
Karczma pod Złotą Wywerną/Karczmą pod Złotą Wywerną); - parent/child inversions (
### Nithalcarrying@lokacja: Izba chorych); - prose fragments that were never locations
(
rozwijanie prędkości na prostych odcinkach).
The importer folds that noise into a clean set before emitting. First it reads the claim ledger back from the previous index — the routing plan the operator may have edited. Unclaimed forms it reconciles against three ground-truth sources:
- The map registry — the canonical surface names + their stripped base names
(the stripper above feeds this). This is the
## Mapaindex section read back viaRead-NerthusIndexMapsonce it exists, else the legacymaps.mdsnapshot. - The Świat gry hierarchy — the hand-authored
Name → @lokacjatree and the world roots (the top-levelŚwiat gry/directories). - The NPC containment tree —
Postaci/NPC/<exterior>/<interior>/…directory nesting is a real (if partial — it cannot cover shared interiors) containment oracle.Postaci/NPC/Thuzal/Rezydencja Tussal/Ogród Tussala/tells usOgród Tussala ⊂ Rezydencja Tussal ⊂ Thuzal.
Nerthus.LocationNormalizer (the mechanical primitives — path split, sibling-fan
detection, subdivision strip, prose detection, fold key) and
Get-NerthusLocationNormalization (the orchestrator that knows the ground-truth
sources and the claim ledger) run this pipeline per interior. Mentions accumulate
in buckets — one working group per (interior, exterior) pair — so each
city's mentions stay separable. Routing precedence per mention:
| Stage | Behavior | Example |
|---|---|---|
| 0 — Claim routing | A claimed form routes to its block, no heuristics; a form claimed by two blocks quarantines as konflikt-roszczeń |
Willa Tussala → the Rezydencja Tussal block |
| 1 — Prose / city-list quarantine | A name or parent that reads like a sentence fragment, or a route of world roots, is quarantined | dzień po zamachu na wiceburmistrz |
| 2 — Path decomposition | Split on / and pick the destination segment (rules below) |
Mury/kwatera główna/Mury/zbrojownia → Mury |
| 3 — Registry resolution | exact-fold → subdivision-strip → declension (single-token names) → fuzzy (BK-tree, distance-bounded, direction-guarded) | Karczmą pod Złotą Wywerną → Karczma pod Złotą Wywerną |
4 — Parent / @outerior repair |
Ground-truth containment overrides the session-derived parent; a route's exterior is the final one (rules below) | Ogród…: @lokacja: Rezydencja Tussal, @outerior: Thuzal |
| 4b — Split-homonym fallback | A name that is the shared bare @alias of ≥ 2 live blocks qualifies by session exterior; no resolvable exterior → quarantine homonim-bez-miasta |
Gildia Teologów in an Ithan session → Gildia Teologów (Ithan) |
| 5 — Accumulation | Variants collapse by folded bucket key; the highest-mention spelling wins the heading unless the bucket is pinned; every surface form is harvested (below) | 27 + 11 + 2 + 1 mentions → one bucket |
| Quarantine | Prose / city-list garbage and claim conflicts are kept as @status: Niepewny |
konflikt-roszczeń |
Claim-routing rules: a mention entry can carry several literal forms — its leaf
name plus each raw @Lokacje string. The normalizer checks them in a
deterministic order (leaf first, then the raw forms sorted); the first claimed
form routes the whole entry. A claim-routed mention takes the claiming block's
own @lokacja as its parent — the heuristics below are never consulted.
Path decomposition rules: a segment repeated inside one path is the
sibling-fan root — the hub the in-game walker kept returning to
(Mury/kwatera główna/Mury/zbrojownia fans around Mury). A path of three or
more distinct segments, at least three of which resolve to world roots, reads
as a city list, not a route, and quarantines — even when the leaf is a non-root
interior (Ithan/Karka-Han/Werbin/Torneg/Eder, but equally
Ithan/Werbin/Eder/zbrojownia): a string of cities is a visited-cities list far
more often than a genuine route, so the pass deliberately quarantines rather
than guessing a parent from weak evidence. Otherwise the last segment that
resolves is the destination.
Parent-repair rules: an inverted root has its parent dropped — a world root
never hangs under its own child. @outerior is the world root reached by
walking the repaired chain. For a travel route the true immediate exterior
is the resolvable segment before the leaf, not the first hop the parser took:
Ithan - Thuzal/Gildia Teologów places the guild in Thuzal, not
Ithan. When the route-fix still guesses wrong, the operator moves that form's
claim line to the right block (below).
Pinning rules: a claim-routed or split-qualified bucket is pinned — a higher-mention spelling never renames a block the operator addressed. A residual fuzzy pass after the merge folds leftover typo pairs neither of which is canonical; it never absorbs a pinned bucket, while an unclaimed typo bucket may still fold into one (typos gravitate to the claim).
Harvesting every session name — claims and @alias¶
The pass harvests every distinct literal string any session used for a
place — the leaf name plus each full @Lokacje raw form (which retains the
exterior/route prefix the parser drops). Every harvested form becomes a claim
line on the emitted block, machine-marked: - @forma_sesyjna: <forma> (auto).
The next run reads those lines back as the routing ledger, so routing is stable
run-over-run.
A clean alternative name (no /) — a spelling, case, or declension
variant — is additionally projected as @alias and indexed by the name
resolver, so a later query that writes Karczmą pod Złotą Wywerną resolves
straight to the canonical Karczma pod Złotą Wywerną. A route/path form
(Nekropolia Karka-han/Karka-han/Karczma pod Złotą Wywerną) is never indexed as
a name — feeding it to the resolver would pollute the index. @alias is never
consulted for routing; only claim lines route. The exterior segments a route
form records are real adjacency evidence and feed the
connectivity graph.
Two Polish-specific guards keep the fuzzy stages honest:
- Directional conflict — opposing cardinal markers never merge:
Izba chorych płn.(północna, north) andIzba chorych płd.(południowa, south) stay two entries. - Parent disagreement — two look-alike names whose resolved parents differ
are not merged:
Mury(⊂ Werbin) stays distinct fromMury Nithal(⊂ Nithal).
The pass is fully deterministic and idempotent — sorted output, fixed
thresholds, no timestamps, no LLM. Re-running the importer against unchanged
sources and an unchanged index reproduces nerthus.entities.md byte-for-byte:
the block merge is a fixpoint. Disk claim lines survive the merge verbatim, and
the machine appends (auto) claims only for forms new this run. Names the
registries cannot anchor (genuine session-only interiors such as Dom Glieve or
Gabinet Przemian Estetycznych) are kept as-is under their resolved parent; only
genuinely-ambiguous entries are flagged Niepewny for review.
Operator edits — what the deterministic pass can't decide¶
Same-named mentions may be homonyms: sessions attest Gildia Teologów in
Thuzal and Ithan — two distinct guild-halls sharing one name — while the
deterministic pass keys by folded name and would collapse them. Whether two
mentions are the same place is world knowledge, so it is the operator's
call, made by editing claim lines in nerthus.entities.md. The claim ledger,
authorship marker, gesture table (adopt / reject / re-route / merge / split /
rename), lint findings, and the reindex workflow are owned by
the entity model;
the location-specific rules live here:
- A split keys by the session-stated city — the ground truth may know only
one chapter (the registry knows only Thuzal's), so keying by it would wrongly
pull the Ithan mentions to Thuzal. Move each claim line where its own prefix
points. Once ≥ 2 live blocks share the bare
@alias, stage 4b qualifies a new bare mention by its session exterior instead of resurrecting a bare block. - Binding fixes the destination itself — leaf and city derivation are
bypassed. A custom lore name that denotes a known place — sessions write
Willa Tussala, the world knowsRezydencja Tussal— is one claim line on theRezydencja Tussalblock. A private instance carved out of a shared interior is a new hand-authored block that claims its literal form. A claim on a form no session has used yet is pre-registration: it routes the form on its first appearance. - Quarantined garbage stays in the file as
@status: Niepewnyblocks — the pass keeps every mention. Adopting a quarantined form is a claim on the right block; the machine's quarantine ghost then disappears on the next reindex, while a hand-authoredNiepewnyblock is preserved.
Test-NerthusClaims lints the ledger read-only, and Invoke-NerthusReindex
(a gated write behind the Council-level location.override capability) applies
hand edits without a daemon restart — it re-runs the full import so moved or
new claims re-route their mentions, and its response embeds the lint so a
conflict never takes effect silently.
How a split affects later resolution¶
A split makes the bare name ambiguous on purpose — Gildia Teologów now
indexes two owners (one per city, via the shared @alias). Resolution
disambiguates by exterior context:
Resolve-NerthusName 'Gildia Teologów' -Type Lokacja # Ambiguous=True, 2 candidates
Resolve-NerthusName 'Gildia Teologów' -Type Lokacja -Within Thuzal # -> Gildia Teologów (Thuzal)
Resolve-NerthusName 'Gildia Teologów' -Type Lokacja -Within Ithan # -> Gildia Teologów (Ithan)
-Within <city> filters the homonym's owners by exterior context — the
mechanics (including the ambiguity report without -Within) are owned by
Name resolution. The session integrator passes the
session's current exterior as -Within, so "the guild" inside a Thuzal session
resolves to the Thuzal hall.
Temporal scope — a same-named place appearing in a new city later¶
Each session-derived Lokacja carries an observation window from the session dates that mention it, written as an open-ended temporal scope on its containment (temporal value semantics: tag schema reference):
- @lokacja: Thuzal (2026-07:) # first seen 2026-07; open-ended = presumed ongoing
- @outerior: Thuzal (2026-07:)
Open-ended is deliberate — absence of later mentions is not evidence of closure,
so a place stays resolvable "now", while -ActiveOn before that month
correctly excludes it (it didn't exist yet). A new Gildia Teologów opening in
Eder in 2027 needs no operator edit: the bare name is already a split family's
shared @alias, so the normalizer emits Gildia Teologów (Eder) keyed by the
session city, and the scopes keep the places apart in time:
Resolve-NerthusName 'Gildia Teologów' -Type Lokacja -Within Thuzal -ActiveOn 2026-07-01 # -> (Thuzal)
Resolve-NerthusName 'Gildia Teologów' -Type Lokacja -Within Eder -ActiveOn 2027-06-01 # -> (Eder)
-Within reads the containment as of -ActiveOn, so space and time compose
for free. A guild that genuinely closes when another opens is an operator
decision — a hand edit closing the containment range; the importer can only
observe first appearance from sessions.
Doors — log-derived @drzwi on Mapa and Lokacja¶
A door is a physical passage between two places, carried as an @drzwi line
on both endpoint blocks — every door is bidirectional. Operators author doors by
hand; the importer derives the rest from game logs at every import/reindex,
marking its lines (auto).
Map doors. A basic-format transcript's location headers are raw Margonem map
names. The importer resolves each header against the map registry by a single
exact fold match on the map's RawName or stripped base name — no fuzzy, and a
header two distinct maps share (the bare Baraki of Baraki p.1/p.2)
resolves to nothing. Two consecutive resolved headers attest a passage: the pair
lands as - @drzwi: <other map> (auto) on both Mapa blocks. Rich log-saver
transcripts carry no location headers (see the log model),
so coverage grows as basic transcripts are fetched into the committed
nerthus.logs/ archive — reindex after a fetch sweep picks them up.
Location doors. Each map-door pair projects through its endpoints'
@lokacja links (operator-set links included — the importer reads them back
from the previous index and its overflow siblings, the claim-ledger pattern; a
range-closed @lokacja never counts as the current link): maps of two
different locations put - @drzwi: <other location> (auto) on both Lokacja
blocks. A pair the hierarchy already implies (a containment edge) or an existing
door line already covers — active or range-closed, on either endpoint — is never
minted. One limitation: the auto line lands only on machine-emitted blocks, so
an endpoint that exists solely as a hand-authored disk block gets no line —
author its reciprocal door by hand.
Operator gestures. A door is one pair, carried as two lines — gestures edit both endpoint blocks (the merge dedups per bare value, marker and range folded off):
- Adopt a machine door: delete the
(auto)marker on both lines. - Reject one: close the range on both lines —
- @drzwi: Ithan (auto) (:2026-07)— and the rejection survives every re-import. Closing only one side leaves the other side's line active (the pair still counts as covered, so nothing is re-minted — but the graph keeps the half-door). Deleting both lines instead resurrects the pair on the next reindex, since the log evidence persists.
The connectivity graphs (two views)¶
Two read routes serve connectivity, both directed:
Get-NerthusLocationGraph — the structural graph, straight from entity
tags:
| Edge type | Built from | Meaning |
|---|---|---|
Containment |
@lokacja chain |
parent → child (Thuzal → Rezydencja Tussal) |
Door |
@drzwi tags |
explicit authored passage between two Lokacje |
Get-NerthusMapTraversalGraph — the observed movement graph, merged from
two sources of location→location transitions:
- the ordered
@Lokacjeentries of each session — each entry contributes its interior part, after the parser normalizesA -> Barrows and-separators into/segments; - each archived game-log transcript's resolved location-segment chain — consecutive duplicates collapsed, one chain per distinct log URL, so sessions sharing a log never double-count.
Every edge carries a Weight and an Origins set (session / log) recording
who attests it, so a move seen in both a route and a transcript is one strong
edge. Edge typing:
| Edge type | Meaning |
|---|---|
Movement |
a move between locations that ARE structurally adjacent (a containment edge or a @drzwi exists, in either direction) |
Teleport |
a move between structurally non-adjacent locations (Plany Astralne, or simply an undocumented passage) — the cue that a @drzwi is missing. The log-derived doors close most of these on reindex; what logs can't see stays for the Council to confirm via Set-NerthusTraversalEntities |
Margonem map checkup — the CDN version sweep¶
Margonem republishes a repainted map under the same base name with a bumped
version suffix: werbin.10.png becomes werbin.11.png, and a never-repainted
eder.png becomes eder.2.png. The checkup (POST /workflows/map-checkup,
Invoke-NerthusMapCheckup) HEAD-probes the next version of every active
Mapa's @url; on a hit it walks upward to the newest version, closes the active
@url line at the current month, and appends the new value marked (auto) —
@url is scalar-historical (see the
tag schema reference), so the retired render
stays queryable — then refreshes @slug from the new filename:
- @url: https://micc.garmory-cdn.cloud/obrazki/npc/rezydencja_tussal.0.png (:2026-07)
- @url: https://micc.garmory-cdn.cloud/obrazki/npc/rezydencja_tussal.1.png (auto) (2026-07:)
A current @url that itself answers 404 with no successor is reported as
failed[].reason = 'gone' — report-only, the cue of a retired or renamed map;
the checkup never touches @status. Politeness is config
(margonem.map_checkup, configuration reference):
a host allow-list, a per-request delay, a probe-step cap, and a failure budget —
a 404 is the expected "no update" answer and never spends the budget. -WhatIf
(?dryRun) reports without writing; maxMaps/cursor slice a batched sweep.
Scheduling — daily on the VM serve loop, per scheduled pipeline in CI — is owned
by Sync & the VM and
the pipelines guide.
The rest of the designed checkup — re-fetching the live Margonem map registry
and diffing it against the index (new ids, renames, stale maps) — stays
deferred. The ## Mapa index section is the source of Mapa identity truth
(see the map registry); a new map
enters via entity creation until the live diff ships.
Cmdlet surface¶
Reads: Find-NerthusLocation, Get-NerthusLocation, Get-NerthusLocationGraph,
Get-NerthusMapTraversalGraph, Test-NerthusClaims. Writes:
New-NerthusLocationEntity, Set-NerthusLocationEntity, New-NerthusMapEntity,
Set-NerthusMapEntity, Set-NerthusTraversalEntities, Invoke-NerthusReindex,
Invoke-NerthusMapCheckup. Routes, parameters, envelopes, and capabilities:
API reference. Resolution queries
(Resolve-NerthusName -Within -ActiveOn) are owned by
Name resolution.
Examples¶
Resolve and inspect an interior location:
Get-NerthusLocation 'Rezydencja Tussal'
# Name : Rezydencja Tussal
# Type : Lokacja
# Location : Thuzal (parent)
# Outerior : Thuzal
# Doors : { Thuzal }
# Children : { }
# SessionForms : { Thuzal/Rezydencja Tussal (auto) }
List the interior locations of Thuzal:
Find-NerthusLocation -Parent 'Thuzal'
Find-NerthusLocation -Outerior 'Thuzal' # transitively, by world root
Register a Margonem map by hand (id + geometry the importer doesn't know):
New-NerthusMapEntity -Name 'Rezydencja Tussal' -MargonemId 4120 `
-Url 'https://micc.garmory-cdn.cloud/obrazki/npc/rezydencja_tussal.0.png' `
-Wymiary '48 x 48' -Typ 'wewnętrzna' -Lokacja 'Rezydencja Tussal'
Split a homonym by hand — the stray route form pinned by its claim line:
### Gildia Teologów (Thuzal)
- @typ: Lokacja
- @lokacja: Thuzal
- @alias: Gildia Teologów
- @forma_sesyjna: Thuzal/Gildia Teologów
- @forma_sesyjna: Ithan - Thuzal/Gildia Teologów
- @status: Aktywny
### Gildia Teologów (Ithan)
- @typ: Lokacja
- @lokacja: Ithan
- @alias: Gildia Teologów
- @forma_sesyjna: Ithan/Gildia Teologów
- @status: Aktywny
Test-NerthusClaims # findingCount 0 — the ledger is consistent
Invoke-NerthusReindex # re-route the mentions; the response embeds the lint
Find missing doors from observed traversals:
(Get-NerthusMapTraversalGraph).Edges | Where-Object Type -eq 'Teleport'
# Ithan → Thuzal (Weight 4, Origins: session, log) — candidate @drzwi via Plany Astralne
Decided and deferred¶
- Override files and a suggest/confirm two-step — retired: claim lines in the index replaced the separate override store; hand-edit governance is git/MR review, and the reindex response embeds the claim lint.
@url_nerthusmirror policy — decided in scope as a tag; when the daemon populates it (on import vs. on a fetch job) is deferred to the log-fetch pipeline.- Auto-applying inferred doors — the importer writes transcript-attested
doors as
(auto)lines at every reindex — re-derivable, rejected by a range-close. A Teleport edge attested only by session routes (no transcript) still promotes manually viaSet-NerthusTraversalEntities. @zawieravs@lokacja— resolved: containment has exactly one direction, the child's@lokacja; the parent-side@zawieraconvenience is retired from the closed schema.@margonemiduniqueness — decided not validated: both registry sources key by id and keep the last entry per id (snapshot ingest and index read-back alike).@isexterior/@inheritsfrom— deferred enrichments, reserved in the closed schema; revisit with the map checkup.
See also¶
- Locations — the introduction — the operator-facing overview
- Architecture — the daemon this model assumes
- The entity model — the 7 types, the block-merge contract, operator block edits
- Tag schema reference — the closed tag table, link values, temporal grammar
- The session model —
@Lokacjemetadata and entity-driven distribution - Name resolution — the Polish-aware resolver,
-Within/-ActiveOn - Adoption — the one-pass import that emits the generated index
- The log model — game-log fetch/parse feeding the traversal graph
- API reference — routes, envelopes, capabilities for every cmdlet above