Skip to content

Tag schema

Lookup reference for the closed, validated, Polish-only @tag schema carried on entity blocks in nerthus.entities.md and its overflow files. It covers the recognized tag set, link-value format, the temporal range grammar and its evaluation rules, the historical write rule, and the projected entity record. The surrounding model — index files, merging, the claim ledger — is the entity model.

The closed tag table

Every recognized tag and its kind:

  • multi — repeatable; evaluation returns every active entry.
  • scalar — single current value; last-active-wins.
  • scalar, historical — scalar whose updates close-and-append instead of replacing (see the historical write rule).
Tag Meaning Kind
@alias Alternative name, added to the resolver's name set — never consulted for mention routing multi
@lokacja The entity's place: its containing location, or on a Mapa the place the map depicts scalar, historical
@drzwi Physical access edge to another Lokacja/Mapa (a "door"); log-derived lines carry (auto) (Locations model) multi
@typ Entity type (authoritative over the ## Category heading) scalar, historical
@należy_do The entity's owner — commonly a Postać's owning Gracz scalar, historical
@grupa Group / faction membership multi
@status Aktywny / Nieaktywny / Usunięty; quarantined location buckets also use Niepewny scalar, historical
@ilość Quantity for stackables (currency, items) scalar, historical
@plik Source file path — the distribution target for session writes (Sessions model) scalar
@generyczne_nazwy Comma-list of generic names added to the resolver (Korony Elanckie, Korony) multi
@nazwa_nerthus Canonical Nerthus display name when the heading is a variant scalar
@koordynaty Position on a parent Mapa (grid x,y) scalar, historical
@slug Margonem URL slug for a Lokacja/Mapa scalar
@margonemid Margonem profile ID (Gracz) or map ID (Mapa) — authoritative identity scalar
@url Primary image URL of any entity (Mapa render, token art, portrait); on a Mapa the CDN image is versioned, so updates keep history scalar, historical
@wymiary Mapa pixel/grid dimensions (W×H) scalar
@pu_startowe PU STARTOWE — starting skill points for a Postać scalar
@pu_suma PU SUMA — total skill points (daemon-computed snapshot) scalar
@pu_zdobyte PU ZDOBYTE = SUMA − STARTOWE (derived) scalar
@pu_nadmiar PU NADMIAR — unbounded monthly-cap overflow pool scalar
@url_nerthus Nerthus-hosted mirror of the primary image scalar
@url_grafika Alternate/additional image URL (battle map, old portrait, variant) multi
@url_dokument Linked external document (Google Docs sheet, pastebin) multi
@url_strona Linked website/page (nerthus.pl article, forum thread) multi
@typ_lokacji Mapa exterior/interior marker (zewnętrzna/wewnętrzna) scalar, historical
@outerior The nearest exterior (world root) a Lokacja resolves to — importer-computed scalar, historical
@inheritsfrom Reserved: an interior inheriting its world position from its Outerior scalar
@isexterior Reserved: the koordynaty/Mapa-evidence exterior classification (deferred) scalar
@first_session Reserved: first session date that mentioned the entity — not currently emitted; first-mention dates surface as the (YYYY-MM:) scopes on @lokacja/@outerior scalar
@floor Recognized on read only: Mapa floor number parsed off a p.N name suffix — never machine-emitted (the importer derives the floor from the map name itself); a hand-authored line is honored on read and projected onto the entity record scalar
@base_name Recognized on read only: Mapa base name with the floor/room suffix stripped — never machine-emitted (the importer derives it from the map name and emits the base name as @alias); a hand-authored line is honored on read, projected onto the entity record, and joins the resolver's name set scalar
@forma_sesyjna A claim: routes one literal session form to this block; a (auto) suffix marks a machine-appended claim (the claim ledger) multi
@duplikat Source files of duplicates dropped at import — machine-owned, re-derived at every merge multi
@prfwebhook Discord webhook — the Gracz block in the committed index is the one source (Players model) scalar
@tematy_zastrzeżone A Gracz's content triggers (Tematy zastrzeżone) as a typed field — the Gracz block holds no freeform prose (Players model) scalar

Note

The PU tags are a snapshot the daemon maintains; PU itself is computed monthly from session PU metadata and is authoritative — PU model. Currency @ilość and transfers: Currency model.

Containment has exactly one direction: the child's @lokacja. The former parent-side @zawiera convenience is retired from the closed schema; a leftover @zawiera line is an UnknownTag integrity error like any other unrecognized key.

On a Mapa, @lokacja is depiction, not containment. It names the place the map depicts — the importer links a map to the Lokacja carrying its base name, and the location tree and the containment graph read Lokacja blocks only, so a Mapa is never a node in the world hierarchy. One place may be depicted by several maps (floors, event copies); each map depicts exactly one place, and map-level @drzwi doors project onto locations through this link (Locations model).

@należy_do names the owner of any entity, not only a character's player. A Przedmiot — a coin holding included — names its holder this way, and that holder may be a Postać, an NPC, a Grupa, a Gracz, or a Lokacja; currency classes a holding by its holder's type (a Postać's coin is physical Margonem items, every other holder's is RP bookkeeping). A holding hangs off exactly one of @należy_do or @lokacja (Currency model).

Closed-schema validation

The recognized-tag set is a fixed list in code, enforced at two seams:

  • On write: unknown tag ⇒ reject. A tag update naming @faction (English) or a typo @grpa is refused with a validation error (HTTP 422) before anything touches disk. There is no generic catch-all. Entity creation validates its seed tags against the same schema, additionally rejects an unknown @typ, and refuses a (name, type) pair a block already carries with 409 — a duplicate heading would merge into one entity at parse time and shadow the new block from every write path.
  • On read: unknown tags are reported, not dropped. The scanner keeps every @key it finds, so a hand-edit is never silently lost; Test-NerthusEntityIntegrity reports the full finding set:
Finding Severity Meaning
UnknownTag Error A tag key outside the closed schema
UnknownType Error A @typ value outside the seven types
InvalidRange Error A (from:to) suffix whose date tokens do not parse
InvertedRange Error A range with from > to
UnresolvedReference Warning @należy_do/@lokacja/@drzwi naming no entity in the model (lore may legitimately lag)
InvalidUrl Warning A @url_* value whose first token is not an absolute http(s) URL
LocationCycle Error A @lokacja containment chain that loops instead of reaching a root

Diacritics are canonical. On disk a tag is its full Polish form: @należy_do, @ilość, @status: Usunięty, type Postać. The daemon writes diacritics only. Exactly two legacy ASCII spellings — @nalezy_do and @ilosc — are recognized on read (model build and write bodies) and normalized to the diacritic key; no other ASCII tag variant is mapped. Legacy-read normalization of values at adoption belongs to the import model.

The URL tags split by role and by what the link points at, so a renderer knows an image from a plain link without fetching it:

Tag Role Kind
@url The entity's one primary image; the Margonem CDN versions map images, so a newer render closes the old line and appends — the map checkup writes the new value with (auto) scalar, historical
@url_nerthus Nerthus-hosted mirror — replace in place scalar
@url_grafika Alternate images multi
@url_dokument Documents multi
@url_strona Websites multi

A link value is: an absolute http(s) URL, then an optional label in double quotes, then the optional (auto) marker, then an optional temporal range — in that order. The URL is the first whitespace-delimited token; the label is display text for the link. Temporal ranges work as on every tag, so retired art stays queryable:

### Rezydencja Tussal
- @typ: Lokacja
- @url_grafika: https://i.imgur.com/QVi9jT2.png "Mapa poglądowa Rezydencji Tussal"
- @url_grafika: https://i.imgur.com/1Lcwrm8.png "Mapa z ogrodem" (:2026-06)
- @url_dokument: https://docs.google.com/document/d/1AbC "Zamówienie na serwis do herbaty"

Integrity validates the shape: a value whose first token does not parse as an absolute http(s) URL is reported as InvalidUrl (Warning — legacy lore may hold odd links). A Gracz profile link needs no tag: it derives from @margonemid.

Temporal ranges

Any tag value may carry a parenthesized validity range after the value:

- @lokacja: Ithan (:2026-07)             # active until Jul 2026 (open start)
- @lokacja: Thuzal (2026-07:)            # active Jul 2026 onward (open-ended)
- @status:  Aktywny (2022-01:2026-06)    # active Jan 2022 – Jun 2026
- @grupa:   Gildia Teologów (2021-01:)   # joined Jan 2021, still a member
  • Format: (VALID_FROM:VALID_TO). Either bound may be omitted for open-ended ranges. A value with no suffix is always-active.
  • Partial dates resolve to bounds: YYYY → the full year, YYYY-MM → the full month (end bound uses the real day count), YYYY-MM-DD → an exact day.
  • Range parsing is uniform — the suffix is recognized on every tag; integrity checks its validity, and the historical write rule below applies only to the nine scalar, historical tags.
  • No seasons. The only temporal primitive is the dated range.

Evaluation rules

"Current" is a computed query, never a stored field. Given an -ActiveOn date (default: now), the daemon evaluates the history:

  • Scalar tags: last-active-wins — among entries whose range covers the query date, the one with the latest start bound. Undated entries tie-break by load order: the later file wins, giving overflow files their primacy over the base index. So Opat Perrin's current Location after a 2026-07 move is Thuzal, not Ithan.
  • Multi-valued tags (@alias, @grupa, @drzwi, @generyczne_nazwy, …): all-active — every entry whose range covers the query date, as a deduplicated set.

The historical write rule

For the nine scalar, historical tags (@lokacja, @status, @należy_do, @ilość, @koordynaty, @typ, @typ_lokacji, @outerior, @url) an update never overwrites the active line. It closes that line's validity range at the write's effective month — the current month, unless the caller passes an explicit date — and appends the new value dated from that month. The block keeps the full timeline. Moving Opat Perrin to Thuzal in July 2026:

- @lokacja: Ithan (:2026-07)              # was: Ithan — closed, not erased
- @lokacja: Thuzal (2026-07:)             # appended

One sanctioned variant: session transfers append their @ilość deltas as new lines dated to the session month, leaving the prior line as is — last-active-wins still resolves the newest balance, and the history keeps every step (Currency model).

A first-ever value is written undated, exactly as the importer writes it; setting the already-active value is a no-op. All other scalar tags (@pu_*, @slug, @wymiary, @plik, …) are tool-refreshed snapshots and replace in place; multi-valued tags always append a new line.

Soft-delete

Removal is the historical write applied to @status: the active status line is range-closed and Usunięty is appended, keeping every bullet. Semantics and consequences are in the entity model; the operator recipe is Add, edit, and retire entities.

The (auto) marker

A value carrying (auto) is machine-written and re-derivable; an unmarked value is operator-authored and preserved verbatim by every merge — the full authorship contract is the entity model. Grammar facts:

  • The marker sits directly after the value (and its label, on a link value), before any temporal range: - @drzwi: Thuzal (auto), - @url: https://…/rezydencja_tussal.1.png (auto) (2026-07:). That order keeps a marked line range-closable.
  • Matching folds the marker off — Thuzal/Rezydencja Tussal (auto) and Thuzal/Rezydencja Tussal are the same claim with different authorship.
  • The daemon strips the marker from every projected value except SessionForms, which keeps claim lines raw.
  • Three entity-tag families carry it today: @forma_sesyjna claims, the log-derived @drzwi doors, and the checkup-refreshed Mapa @url (Locations model owns the latter two derivations).
  • The marker is not entity-only. A session's @Pliki section — the distribution target list the tool writes and rewrites whole — carries it on the section line: - @Pliki: (auto) (Sessions model). The section has no value of its own, so the marker sits where a value would. It means what it means everywhere else: the tool authored this and owns it, so an operator edit is overwritten on the next pass.

The projected entity record

The daemon parses the Markdown into a structured object; cmdlets return this shape with temporal fields already resolved to the requested -ActiveOn date. All values are the raw tag strings — the daemon does no numeric coercion (Quantity is "50", not an integer).

Field Source tag(s) Notes
Name ### heading text, overridden by @nazwa_nerthus Canonical name
Heading ### heading text The literal block heading
Type @typ (fallback: the ## Category) One of the seven types
Category the ## Category heading Grouping only
Status @status (default Aktywny) See the tag table
Aliases @alias + @generyczne_nazwy All-active alternative names
Owner @należy_do The entity's owner
Groups @grupa All-active memberships
Location @lokacja Last-active place
Doors @drzwi All-active access edges
Quantity @ilość Stackables/currency
FilePath @plik Distribution target
Coordinates @koordynaty Grid position on parent Mapa
RestrictedTopics @tematy_zastrzeżone A Gracz's content triggers
SessionForms @forma_sesyjna Claim lines, raw — the (auto) marker included
DuplicatesDropped @duplikat Duplicate sources dropped at import

Tag-presence fields are added to the record of any entity carrying the tag — a Gracz has a MargonemId; a Mapa has MargonemId, Url, Slug, Dimensions. The set: PuStartowe, PuSuma, PuZdobyte, PuNadmiar (the Postać PU snapshot), plus MargonemId, Url, UrlNerthus, Slug, Dimensions, Outerior, InheritsFrom, IsExterior, Floor, BaseName, and the all-active link arrays ImageUrls (@url_grafika), DocumentUrls (@url_dokument), WebsiteUrls (@url_strona) — link values raw, the quoted label included.

Routes, envelopes, and the cmdlet ⇄ route mapping for entity reads and writes: API reference. How @alias and @generyczne_nazwy feed the resolver: Name resolution.