Skip to content

Session metadata

Nerthus.Core (until cutover). This page describes the frozen system that runs today and is deleted at cutover. Replaced by: not yet written.

Exact syntax reference for the session header and every structured metadata key a session block may carry. For what the daemon does with this data — distribution, integrity hashing, the participation graph — see the session model.

Session header

### 2026-07-01, Eraster rozmawia z Tussalem, Anward
    └── date ──┘└ title (may contain commas)┘└ narrator ┘

The header is the universal key: dedup, content hashing, participation, and distribution all key on it. Comparison is case-insensitive. The header is canonical and immutable — the tool never rewrites it.

Field Rule
Date The first 10 characters, YYYY-MM-DD; multi-day suffixes below
Title Everything between the date and the last comma — inner commas are preserved
Narrator The text after the last comma, trimmed, with trailing *, , and ; removed

Parsing notes:

  • Any ### heading starting with a YYYY-MM-DD-shaped prefix is treated as a session header — even one with a malformed day part (2026-07-001). That tolerance enables the @Data rescue below.
  • A missing comma right after the date is tolerated.
  • The narrator is never resolved to an entity — it stays a raw string. Rada denotes a Rada session.

The narrator field is read as people

A header names one narrator, several, or none, and the field is read two ways at once: the raw string, and that string split into the people in it.

### 2026-07-01, Obrady Rady, Shalley + MD
  • Several narrators are joined with +, spaces included. Each part is a name in its own right. Only + splits: a header writing the Polish conjunction — Rothe i Materios Dragonius — stays one string, because i is also an ordinary word and splitting on it would turn a fragment of a title into two narrators.
  • A field that is not a name yields no narrator. A name is capitalised, so a last field whose first letter is lowercase is prose — a title that ran on past a comma — and the evening is recorded as having no narrator rather than one called zielarstwa i przywrócenia. Only the first letter decides: Lorenzo de Clemente and Moira von tepes are names, and a rule reading every word would refuse them.
  • An empty last field — a header ending in a comma — is no narrator either.
  • A field with no letter at all, such as ???, gets no verdict and is kept.

An evening left without a narrator this way is not silently dropped: the normalization worklist reports the refused fragment so the header can be repaired. Use @Narrator below to state the narrators without touching the header, which is immutable.

Multi-day suffixes

Form Example Start End
Same-month /DD 2022-12-21/22 2022-12-21 2022-12-22
Cross-month /MM-DD 2023-01-31/02-01 2023-01-31 2023-02-01

The end date is composed textually from the start's year (and month, for /DD) plus the suffix; it is never range-checked against the start.

Recognition rules

  • Session-bearing roots — the daemon scans *.md under exactly four directories: Wątki/, Organizacje/, Postaci/, Świat gry/. A session block anywhere else is invisible. That includes the generated Lokacje/ registry at the repo root — its files hold session copies and are named what the scanner looks for, and they are still never read back (session registry).
  • Bare and @ forms are one format — the @ prefix is the canonical spelling. - PU: parses identically to - @PU:. When the tool authors or edits metadata it writes the @-tag form; it never rewrites a hand-authored file to change its shape.
  • Directive recognition is prefix-based. These stems map a label to a structured tag:

    Stem Tag
    narrator- @Narrator
    data- @Data
    tytu- @Tytuł
    lokacj- / lokalizacj- @Lokacje
    log- @Logi
    exact PU @PU
    transfer- @Transfer
    intel- @Intel
    uczestnic- @Uczestnicy

    A custom label sharing a stem (- Logistyka:@Logi) is read as structured — pick narrative labels off those stems. Parsing never alters the file text.

  • What the parser accepts is wider than what the tool writes, and until 2026-09-01 this page said nothing about it at all. That silence had a cost: the word "spaces" appeared nowhere on 345 lines, and a narrator who copied the shape below and typed two spaces instead of four produced a session whose @Lokacje and @PU the API silently could not read — 540 sessions, one in four of the last three years (project 116). An undocumented constraint that drops data without an error is not a constraint; the parser was widened rather than the corpus reflowed, and the accepted set is now written down so nobody has to discover it.

    accepted
    child indent any indent greater than its tag's — 2, 3, 4 spaces or a tab. A child at the tag's own indent is read too, but only where the field's grammar accepts the whole line: an award that is Name: 0.2 end to end, a log line that IS a URL, a location with no colon in it
    bullets -, *, +, . A dash needs no space after it (-PU: parses); *, + and do, because *text is emphasis and * text is a list item
    whitespace tabs count as four; NBSP, figure space and narrow NBSP count as one; zero-width characters and the BOM are removed before the line is read
    tags @-prefixed or bare, colon optional (- PU opens the list), any casing (LOGI:)
    decimals Polish comma or dot. - @PU: A: 0,1, B: 0,2 is two awards, not one
    award unit written out or omitted — 0,2 PU is 0,2. Only when the rest of the value is a number: 0,2 pkt is not an award
    awards the colon may be missing — - Aurelia Lesup 0.1 is an award
    Gen2 italics the line must open with *; what it closes with is read loosely — *Lokacja: X*\, *Lokacja: X*., an unclosed *Lokacja: X, and *Logi:* https://… where only the label is emphasised
    log links a scheme is optional: pastebin.com/raw/x is read as https://pastebin.com/raw/x

    The canonical shape below is still the one to write. Accepted is not the same as recommended, and the tool authors 4-space children and @-tags.

  • Rendering is fixed@Data is inline; every other tag's items are 4-space nested children on a single flat level (a nested interior is written as its full Parent/Interior path on one line). Empty tags are omitted. The authored tag order is the order of the key table below; the tool-managed @Pliki section is always rewritten at the end of the block.

  • Casing convention — session tags are capitalized, usually plural (@Lokacje, @Pliki, @PU); entity-index tags are lowercase singular (@lokacja, @plik). They are different tags in different files — see the tag schema.

Metadata keys

Tag Shape Meaning
@Narrator nested name(s) Narrator override in metadata; the header stays untouched
@Data inline YYYY-MM-DD Date override — rescues a malformed header date
@Tytuł inline text Title override — rescues a misspelled header title
@Lokacje nested locations Where it happened, as Parent/Interior paths
@Logi nested URLs Game-log links, fetched into the nerthus.logs/ archive
@PU Postać: 0,3 Per-character PU grants — drive the monthly PU batch
@Transfer [amount] denom, src -> dst Currency/item movements
@Intel target: message Directed notifications, delivered via Discord when the session is closed
@Uczestnicy nested names Explicit participant roster without a PU grant
@Pliki: (auto) nested paths Distribution bookkeeping — machine-authored, overwritten on the next distribution

@Narrator

Replaces the header-parsed narrator in parsed metadata. The header itself — the key — is never rewritten.

- @Narrator:
    - Anward

Repeat the tag for an evening several people adjudicated, one line each. The first @Narrator line replaces whatever the header gave; the ones after it add to that answer.

- @Narrator: Shalley
- @Narrator: MD

This is the authored form of the + header field, and it is what repairs a header the grammar refused. The name test the header path applies does not run here: writing the field out by hand is a statement of what it is, and the tag exists to correct a header rather than to be second-guessed.

@Data

Inline date override. Validation is shape-only (\d{4}-\d{2}-\d{2}): a value that fails the shape is ignored, but a calendar-impossible value that fits it (2024-13-40) still overrides the date — integrity later flags it as MalformedDate.

### 2026-07-001, Eraster rozmawia z Tussalem, Anward   ← broken header date
- @Data: 2026-07-01                                    ← override fixes the parsed date; header key stays

@Tytuł

Inline title override. Any non-empty value wins; there is no shape to validate. Recognition is the tytu- stem, so the ASCII spelling @Tytul sets the title as well: a header broken badly enough to need the override may have lost its diacritics too. The stem is wide, and a narrative label picked off it (- Tytulatura:) overrides the parsed title rather than staying prose. A nested list under the tag sets the title from its last non-empty child.

### 2026-07-01, Eraster rowzmaria z Thuzalem, Anward   ← misspelled header title
- @Tytuł: Eraster rozmawia z Tussalem                  ← override fixes the parsed title; header key stays

@Lokacje

Nested location references as Parent/Interior paths. An -> movement route always normalizes into / path segments and feeds the map traversal graph (location model). A legacy - separator is ambiguous, since close to a quarter of the Margonem catalogue spells its own interiors that way — Przeklęty Zamek - komnata is one map — so the location service resolves the whole segment against the map registry first and splits on the separator only when that fails. Author / today.

- @Lokacje:
    - Thuzal
    - Thuzal/Rezydencja Tussal
    - Thuzal -> Rezydencja Tussal -> Ogród Tussala

@Logi

Nested game-log links. The parser extracts http(s):// and file:// URLs into metadata — file:// is admitted for local transcripts; a bare local path with no scheme stays in the source but never reaches the parsed record. The log-fetch step archives each URL under nerthus.logs/ (log model).

- @Logi:
    - https://nerthus.pl/logizsesjierasteraztussalem

Note

@Logi names game transcripts; @Intel triggers Discord delivery. They are entirely different systems and never overlap.

@PU

One Postać: value line per character. The Polish comma-decimal is accepted (0,20.2), and the unit may be written out0,2 PU is the same award as 0,2, in any casing, with or without the space. These grants drive the monthly PU batch (PU model).

The unit is read only when the rest of the value is a number and nothing else. 0,2 pkt, ~0,2 and 0.5 + 0.2 (za dobre rozegranie) are not awards this grammar defines — write the number.

- @PU:
    - Eraster: 0,2
    - Lord Tussal: 0,2

@Transfer

[amount] denomination, source -> target — coin, stackable item, or unique item. Endpoints are in-game entities only, never a Narrator. Syntax and application rules are owned by the currency model.

- @Transfer:
    - 10 Korony, Eraster -> Lord Tussal
    - 2 Dzbanek herbaty, Lord Tussal -> Eraster

@Intel

target: message lines — directed notifications the bot delivers to the target's Discord channel (log model).

- @Intel:
    - Thuzal: Mieszkańcy Thuzal widzieli, że Eraster odwiedził Lorda Tussal po południu w jego rezydencji.

@Uczestnicy

Explicit participant names — attendance without a PU grant. @PU and @Uczestnicy are unioned for distribution; the names resolve through the name resolver with fuzzy matching disabled.

- @Uczestnicy:
    - Opat Perrin

@Pliki

The resolved distribution target set, recorded by the tool. It is not parsed as structured metadata — the live file set is discovered by scanning — and hand edits to it are overwritten wholesale on the next distribution.

It carries the (auto) marker on its section line, the same authorship marker entity tags use (tag schema): the tool wrote this section and owns it. The section has no value of its own, so the marker sits where a value would.

- @Pliki: (auto)
    - Postaci/Gracze/Eraster.md
    - Wątki/Intrygi w Thuzal.md

The section-matching regex is not end-anchored, so an unmarked @Pliki written before the marker existed still matches and is replaced in place on the next distribution.

Nierozpoznane

What the parser still refuses, with the counts measured on the corpus pin b46b0da0 (2026-09-01). Each is reported by GET /sessions/integrity, and none can be repaired by the daemon: a session header is the deduplication key and the PU ledger's key, so changing one is an operator's act.

shape count issue the repair
the header names no narrator 92 NarratorAbsent add - @Narrator: <name> to the body
the last comma field is not a name 10 NarratorDropped the same — the title contained a comma, so the field was the title's tail. The row carries the refused text
@Logi holds something that is not a link 7 LogsUnparsed ask the narrator for the URL. A pasted transcript, an apology, or a repo-relative /Logi/... path, which has no URL scheme because nothing consumes that directory
the header's date is not a real date 1 MalformedDate fix the header — 2024-25-08 has no month 25, so the session sorts into a month nothing returns

@Narrator is the repair for the first two by design: the tag path skips the name test that the header path applies, precisely so an operator can overrule the grammar. The grammar refuses a last field beginning with a lowercase letter because that is how a title with a comma in it looks, and relaxing it costs 68 header fields across four narrators the index resolves — Lorenzo de Clemente, Valdis la Douce, Kyria la Blachette, Moira von tepes. Voiding a name the index reaches is the one thing that rule must never do.

Custom narrative tags

Any other - <Etykieta>: block — - Efekty: (session outcomes), - Objaśnienia: (clarifications) — is preserved verbatim and carries no structured weight: it moves no PU, no currency, and no participation. Zmiany is not a structured directive; entity mutations are entity writes, never session directives.

- PU:
    - Eraster: 0,2
- Efekty:                                            ← custom narrative tag (preserved, not parsed)
    - Eraster wręczył Tussalowi łapówkę w postaci dziesięciu koron.
    - Zjednał sobie przychylność Lorda Tussal.

Legacy generations (read-only)

The pre-tool repository holds three older shapes. The daemon auto-detects and reads all of them as-is; the parser reports which shape a body used (Gen1/Gen2/Gen3/Aktualny). No corpus-wide normalization pass exists.

Shape Era Locations Logs Metadata
Gen1 –2022 none Logi: <url> plain line none
Gen2 2022–2023 italic label line, below Logi: <url> plain line none
Gen3 2024–2025 - Lokalizacje: list - Logi: list - PU:, - Efekty: (no @)
Aktualny 2026+ - @Lokacje: - @Logi: full @-tag set (the authored form)

An example block (in Wątki/Intrygi w Thuzal.md) showing the Gen3 shape:

### 2025-01-26, Eraster spotyka się z Tussalem w Thuzal i wraz z nim planuje dalsze działania, Anward
*Lokalizacje: Thuzal, Thuzal/Rezydencja Tussal*

… prose …

- Logi: https://nerthus.pl/logizsesjierasteraztussalem
- Efekty:
    - Eraster otrzymał zaproszenie do rezydencji Lorda Tussal …
- PU:
    - Eraster: 0.2

The italic location line

A Gen2 or Gen3 body writes the evening's places on one italic line of its own. The label takes either stem, in any case: Lokacje, Lokacja, Lokalizacje, Lokalizacja — the two stems the lokacj- / lokalizacj- directive already takes.

*Lokacje: Karka-han/Karczma pod Złotą Wywerną*
*Lokalizacje: Przełęcz Dwóch Koron*
*Lokacje: Tuzmer; Płaskowyż Arpan*
*Lokalizacje Nithal, Podgrodzie Nithal*

The label goes together with whatever separates it from the first name — a colon, a semicolon, a comma, or a run of spaces before a capitalised word. What remains splits on commas and semicolons, and each name follows the @Lokacje rules above. A label with nothing after it names no place.

Lokacja is an ordinary Polish noun as well as a field name, so the case of the following word decides the line: *Lokacja jest piękna* is body prose and names nothing.

Two more italic shapes are read. A bare semicolon-separated list with no label at all is taken as locations, and a line opening on Logi contributes its URLs to @Logi.

See also

  • Session model — distribution, integrity tiers, the participation graph
  • Tag schema — the entity-index tags (@lokacja, @plik)
  • API — the session routes, cmdlet mapping, envelopes