The roster model¶
The roster subsystem defines the two people-facing entity types: Gracz — the real
person, keyed by their Margonem profile ID — and Postać — an in-fiction character
owned by exactly one Gracz. This page specifies the exact contracts: the durable
roster sections of the index, the retired Gracze.md adoption source and its seed
parse rules, the roster invariants, and the API-managed Dodatkowe informacje
entries. For the plain introduction, start with the operator page.
Concepts¶
- Gracz — the real person. Identity is the Margonem profile ID (a stable
integer like
9826541); nicks are fungible, and each Margonem account caps at six game characters. The friendly block heading (e.g.### Eraster) is a display label. - Postać — an in-fiction character (e.g.
Eraster,Losso Minewit,Felkissian). Linked to its owner by@należy_do; has its own entity record, currency holdings, session participation, and PU accounting. - Active character — each Gracz designates one "current" Postać:
@status: Aktywnyon that Postać,@status: Nieaktywnyon the others. InGracze.mdthis was encoded by bold-linking exactly one character. - PRFWebhook — the player's personal Discord webhook, carried as
@prfwebhookon the Gracz block; PU and Intel notifications for any of that player's characters are delivered there. See Logs & Discord. - Tematy zastrzeżone — content triggers the player does not wish to see in
sessions they take part in (e.g.
Banany), carried as the typed scalar@tematy_zastrzeżoneon the Gracz block and projected asRestrictedTopics. Narrators consult it when planning a session.
The split is deliberate: PU, the webhook, triggers, and identity attach to the person; currency, reputation, sessions, and the character sheet attach to the character. One person, several characters, one active.
The roster record: the durable index sections¶
The roster lives in the ## Gracze and ## Postacie Graczy sections of
nerthus.entities.md. The import's bootstrap path seeds them from Gracze.md
(Adoption); once present they are durable — never
regenerated, and preserved on re-import because they are there. Operators
hand-edit them freely (the index header invites it) and the daemon writes them
through the roster API. The durable/machine section split and the block format
itself (### heading + flat @tag bullets) are specified in
the entity model.
## Gracze
### Eraster
- @typ: Gracz
- @margonemid: 9826541
- @prfwebhook: https://discord.com/api/webhooks/1211971813370892408/-7i1JynZrz…
- @status: Aktywny
- @tematy_zastrzeżone: Banany
## Postacie Graczy
### Eraster
- @typ: Postać
- @należy_do: Eraster
- @status: Aktywny
- @plik: /Postaci/Gracze/Eraster.md
- @pu_startowe: 20
- @pu_suma: 127.90
- @pu_zdobyte: 107.90
- @pu_nadmiar: 0.00
Eraster's other three Postacie (Losso Minewit, Felkissian, Klusp) follow the
same shape with @status: Nieaktywny; legacy decimal scale is preserved as written
(42.1, 0.00), and @pu_zdobyte is derived as SUMA − STARTOWE.
Field reference (Gracz / Postać entities; the full tag schema is in the tag reference):
| Tag | On | Meaning |
|---|---|---|
@typ |
both | Gracz or Postać |
@margonemid |
Gracz | authoritative profile ID (identity key) |
@należy_do |
Postać | owning Gracz (links character → person) |
@status |
both | Aktywny / Nieaktywny / Usunięty — soft-delete only (entity model) |
@alias |
Postać | alternate names (Lord Tussal known as Tussal) |
@plik |
Postać | the character source file — the distribution target for sessions |
@prfwebhook |
Gracz | the player's Discord webhook — the committed index is its one and only source |
@tematy_zastrzeżone |
Gracz | the player's content triggers as a typed scalar; the Gracz block carries no freeform prose (projected as RestrictedTopics) |
@pu_startowe @pu_suma @pu_zdobyte @pu_nadmiar |
Postać | the four PU accounting fields — semantics owned by the PU model |
Two rows carry a design decision:
- A Gracz carries no
@plik, so session distribution skips a Gracz participant. The legacyGracze.mdroster carried@plik: Gracze.md— distribution would then recreate the deletedGracze.mdon every settle run. The import drops the tag. @prfwebhookresolution. Delivery looks up the entity's own@prfwebhookfirst, then the owning Gracz's, else it records a skip. The committed index is the one webhook source; the same URLs were already committed insideGracze.md, so the exposure class is unchanged (Logs & Discord).
Identity over nick
The Gracz heading and @należy_do carry the friendly nick for human
readability, but identity always reconciles to @margonemid; a nick change
updates only the label and @alias. See
identity & permissions.
The retired seed source: Gracze.md¶
Gracze.md was the hand-authored player database and the roster's adoption
source. The import's bootstrap path reads it once; once the durable index
sections above exist, they are the roster record and a re-import preserves them
by presence (the bootstrap path no longer runs). The tool never writes to or
deletes Gracze.md — retiring the file is the operator's own git commit after
adoption, and git history preserves the archive. Adoption aborts with a
structured RosterSourceMissing error only when there is neither a durable
## Gracze section nor a Gracze.md (Adoption).
The seed parses a fixed per-player block shape:
### Eraster
- Postaci:
- **[Eraster](/Postaci/Gracze/Eraster.md)**:
- PU: NADMIAR: 0.00, STARTOWE: 20, SUMA: 127.90
- Wręczył Tussalowi łapówkę w postaci dziesięciu koron
- [Losso Minewit](/Postaci/Gracze/Losso Minewit.md):
- PU: NADMIAR: 0.00, STARTOWE: 40, SUMA: 42.1
- [Felkissian](/Postaci/Gracze/Felkissian.md):
- PU: NADMIAR: 0.00, STARTOWE: 41, SUMA: 52.95
- [Klusp](/Postaci/Gracze/Klusp.md):
- PU: NADMIAR: 0.00, STARTOWE: 64.00, SUMA: 65.40
- Tematy zastrzeżone: Banany
- PRFWebhook: https://discord.com/api/webhooks/1211971813370892408/-7i1JynZrz…
- ID Margonem: 9826541
Parse rules the seed applies:
| Source line | Meaning | Maps to |
|---|---|---|
### Eraster |
friendly label for the player block | Gracz heading (display name) |
**[Char](…)** (bold link) |
the active character | Postać @status: Aktywny |
[Char](…) (plain link) |
an inactive character | Postać @status: Nieaktywny |
(/Postaci/Gracze/Char.md) |
the character's source file | Postać @plik |
PU: NADMIAR: …, STARTOWE: …, SUMA: … |
per-character PU baseline | @pu_nadmiar / @pu_startowe / @pu_suma (+ derived @pu_zdobyte) |
Aliasy: Tussal |
alternate names | Postać @alias |
free bullet under a character (Wręczył Tussalowi łapówkę…) |
narrator note | moved into the charfile's **Dodatkowe informacje:** |
Tematy zastrzeżone: Banany |
content triggers (player-level) | Gracz @tematy_zastrzeżone (typed scalar) |
other player-level bullets (e.g. Tussal's - Inne:) |
roster notes | moved into the player's active-character **Dodatkowe informacje:** |
PRFWebhook: … |
personal Discord webhook | Gracz @prfwebhook |
ID Margonem: 9826541 |
authoritative identity | Gracz @margonemid (a #char profile URL is not carried onto the block) |
Reading notes:
- The seed parses only the
## Listasection and skips fenced example blocks and unrecognized lines. The narrator-note bullets under each character are moved into that character's file, deduplicated against notes already there; the move mechanics (fail-early, per-bullet logging) belong to the import model. - The PU line accepts
ZDOBYTE:in place ofSUMA:— the seed reconstructsSUMA = STARTOWE + ZDOBYTE. Decimals accept dots or commas (42,1→42.1). BRAKis the uninitialized sentinel (this page owns its semantics):STARTOWE: BRAK— or a missing PU line — emits no PU tags, except that a numericSUMAbesideSTARTOWE: BRAKstill emits@pu_suma;NADMIAR: BRAKseeds@pu_nadmiar: 0. A ripple worth knowing: on such a block the first monthly award computes@pu_zdobyteagainst a missing STARTOWE (treated as 0), so it lands as the full legacy total — set@pu_startoweby hand or API first when the baseline matters for a new-character STARTOWE preview.ID Margonemmay be a bare id or a profile URL (…/profile/view,9826541#…) — the numeric id is extracted either way and kept as@margonemid. The full URL's#charcharacter-sheet anchor is not carried onto the Gracz block: the character's own**Karta Postaci:**link is that sheet's home, and git history keeps the retiredGracze.mdverbatim.PRFWebhook: BRAKemits no@prfwebhook.- The PU values seeded here are the adoption baseline only; going forward PU is
computed and authoritative from session
@PU(PU model). - A player-character file with no roster entry still seeds as a Postać —
@typ/@status/@plikonly, no@należy_do. Files underPostaci/Gracze/Martwi/seed asNieaktywny.
The character file: Postaci/Gracze/<Char>.md¶
Each Postać has a hand-authored Markdown file. It is a human-authored Polish source —
the daemon reads it, distributes sessions into it (resolved via @plik), and
manages the Dodatkowe informacje entries in place — but it never rewrites the
freeform prose. Bold-label fields:
| Label | Contents |
|---|---|
**Karta Postaci:** |
link to the freeform character sheet on the Margonem profile (e.g. https://www.margonem.pl/profile/view,9826541#char_22788,nerthus) |
**Tematy zastrzeżone:** |
optional character-level trigger restatement |
**Stan:** |
current condition (Zdrowy., Brak., injury notes) |
**Przedmioty specjalne:** |
narratively significant items (everyday holdings live in entity currency/items) |
**Reputacja:** |
three buckets — Pozytywna / Neutralna / Negatywna, each a per-town list (Eder, Torneg, Ithan, …) |
**Dodatkowe informacje:** |
standing facts and hooks (blessings, debts, owed items) — API-managed entries, below |
**Opisane sesje:** |
a [[_TOC_]] followed by one block per session |
The Karta Postaci is freeform and external (the Margonem profile); the tool tracks only the link — modeling statistics or the diceless ruleset is a deliberate non-goal (Architecture).
Under **Opisane sesje:**, session distribution appends one block per session the
character took part in, keyed by the universal session header and copied verbatim
from the session record — the character file is a projection, not an authority.
Distribution mechanics: the session model.
Dodatkowe informacje — the API-managed entries¶
Each bullet under **Dodatkowe informacje:** is one entry the daemon lists, adds,
edits, and removes in place (the reversed legacy label **Informacje
dodatkowe:** is accepted on read — one legacy charfile uses it), through
Get-NerthusCharacterInfo, Add-NerthusCharacterInfo,
Set-NerthusCharacterInfo, and Remove-NerthusCharacterInfo. An entry may carry
the standard temporal (from:to) range, filtered by the all-active rule
(entity model). An edit or removal must restate the
entry's current text and is refused on a mismatch, so concurrent operators never
silently overwrite each other. Entry text is a single line by contract — the API
refuses embedded newlines, so an entry can never smuggle a fake session heading
into the sheet. The listed text is authoritative; the range-stripped value
also drops a trailing colon-bearing parenthetical, while matching and filtering
stay exact. The import moves the Gracze.md narrator notes into this section
during adoption — a character's own notes into that character's file, and each
player's free roster notes (- Inne: …) into their active character's file
(Adoption). Routes, bodies, and capabilities:
the API reference.
Behavior & invariants¶
- One owner, one active. Every Postać has exactly one
@należy_do. Setting a Postać's@statustoAktywnydemotes the owner's previously active Postać toNieaktywnyin the same write; both changes are audited. Creation is the one path that does not demote — a new Postać seeds the entity-create default@status: Aktywnywithout touching siblings, so promote/demote explicitly after creating an additional Postać. Status is a temporal tag — each flip is a dated append, never an overwrite (entity model). - Soft-delete only. Removing a Gracz or Postać is a
@status: Usuniętytransition (entity model); the record and its file persist. - The durable sections are the roster record. All roster mutations land in
## Gracze/## Postacie Graczy(above); the tool never touchesGracze.md. - PU is computed, not edited. Roster cmdlets do not set PU; it flows from the
monthly batch (PU model). Creating a Postać seeds no
PU tags —
Get-NerthusNewPlayerCharacterPUCountpreviews the policy-derivedSTARTOWE, which the operator passes explicitly (e.g.-Tags @{ pu_startowe = '41' }). - Margonem id at creation. Creating a Gracz does not require
@margonemid, but a Gracz without one cannot be identity-linked (the Margonem/Discord login, identity & permissions) — pass it in-Tagsat creation. - Ownership-scoped writes. Roster writes — the webhook and character-info
routes included — require the
player.writecapability (player.write.ownfor the gracz role, scoped to the caller's own Gracz and Postacie) — the scoping rules are owned by identity & permissions; capability ids are listed in the capability reference. - Single write gate. Every roster mutation passes the write gate and fires the write hooks — see the API reference.
Cmdlet surface¶
Players: Get-NerthusPlayer, New-NerthusPlayer, Set-NerthusPlayer,
Remove-NerthusPlayer. Player webhook: Get-NerthusPlayerWebhook,
Set-NerthusPlayerWebhook (an empty value clears it to BRAK). Characters:
Get-NerthusPlayerCharacter, New-NerthusPlayerCharacter,
Set-NerthusPlayerCharacter, Remove-NerthusPlayerCharacter. Character info
entries: Get-NerthusCharacterInfo, Add-NerthusCharacterInfo,
Set-NerthusCharacterInfo, Remove-NerthusCharacterInfo. Starting-PU preview:
Get-NerthusNewPlayerCharacterPUCount. A character's owning Gracz is also
resolvable through the API's entity surface. Routes, parameters, envelopes, and
capabilities: the API reference.
Examples¶
Eraster is one Gracz (@margonemid: 9826541) owning four characters, with
Eraster active:
PS> Get-NerthusPlayerCharacter -Name Eraster | Format-Table Name, Status, PuSuma
Name Status PuSuma
---- ------ ------
Eraster Aktywny 127.90
Losso Minewit Nieaktywny 42.1
Felkissian Nieaktywny 52.95
Klusp Nieaktywny 65.40
Switching the active character (demotes Eraster, promotes Felkissian in one write):
Previewing the starting PU for a new character of Eraster's, then passing it at creation:
PS> Get-NerthusNewPlayerCharacterPUCount -Player Eraster
PS> New-NerthusPlayerCharacter -Name 'Felkissian' -Tags @{ 'należy_do' = 'Eraster'; plik = '/Postaci/Gracze/Felkissian.md'; pu_startowe = '41' }
Reading Eraster's webhook and adding a Dodatkowe informacje entry:
PS> Get-NerthusPlayerWebhook -Name Eraster
PS> Add-NerthusCharacterInfo -Name Eraster -Text 'Wręczył Tussalowi łapówkę w postaci dziesięciu koron'
Design decisions¶
- Nick-change history. Old nicks are retained as
@alias; a dated nick-change ledger is deferred to identity work (identity & permissions). - Player-level vs character-level triggers. The Gracz value is authoritative; character-file restatements are informational.
- Creation vs the one-active flag. Enforced on update, not creation — see "One owner, one active" above.
- The roster record. Adoption retires
Gracze.md; the durable index sections are the roster record; deleting the file is the operator's commit. - Webhook storage.
@prfwebhookon the Gracz block in the committed index is the one and only source; the webhook secrets file is retired.
See also¶
- Entity model — the 7-type model, the durable/machine index split, soft-delete; temporal scopes and the closed tag table are in the tag reference
- Session model — session headers and entity-driven distribution into character files
- PU model — the
STARTOWE/SUMA/ZDOBYTE/NADMIARaccounting and the monthly batch - Adoption — the bootstrap path and the notes move
- Name resolution — resolving nicks, aliases, and declined forms
- Identity & permissions — Margonem identity and capabilities
- Logs & Discord — PRFWebhook delivery
- Architecture · Glossary