Skip to content

Currency and economy model

Nerthus has an in-fiction economy denominated in three coin types. The tool tracks who holds how much, where it moves, and whether the books balance. Every coin holding is a Przedmiot entity with a denomination tag and a temporal quantity; every transfer is a session directive that nets to zero. This page defines the denomination data table, the currency-entity shape, the @Transfer directive, the physical/virtual supply split, the reconciliation invariant, and the economic reports built on top of them. For a plain introduction, start at the currency overview.

Concepts

  • Denomination — a coin type from the Council data table (.nerthus/data-tables/currency.json). Three exist: Korony Elanckie (Gold), Talary Hirońskie (Silver), Kogi Skeltvorskie (Copper). Kogi is the base unit; the others are fixed multiples of it.
  • Currency entity — a Przedmiot whose @generyczne_nazwy resolves to a denomination. It is owned by exactly one holder (@należy_do) or lies unowned at a place (@lokacja), and carries a temporal @ilość.
  • Holder — any entity that can own coin: a Postać, NPC, Grupa, Gracz, or a Lokacja (a place that holds a treasury — e.g. a town vault or a temple hoard). A holder's type decides whether its coin is physical (real Margonem items) or virtual (RP bookkeeping only).
  • Kogi arithmetic — all sums, deltas, comparisons, and the Gini computation (the standard 0-to-1 wealth-inequality measure) run in Kogi (base units) via Nerthus.EconomicAnalyzer. Every report field is a raw Kogi value (Kogi, WealthKogi, PhysicalKogi); sign is preserved, so a debt of 35 250 Kogi reads as -35250.
  • Supply — total Kogi-equivalent of a denomination across all holders.

The denomination data table

Defined in .nerthus/data-tables/currency.json (Council-editable, seeded only when the file is absent; see Configuration); the daemon serves it read-only (route in the API reference).

Canonical (@generyczne_nazwy) Short Tier Kogi multiplier Stem
Korony Elanckie Korony Gold (złoto) 10000 kor
Talary Hirońskie Talary Silver (srebro) 100 tal
Kogi Skeltvorskie Kogi Copper (miedź) 1 kog

Exchange: 100 Kogi = 1 Talar, 100 Talarów = 1 Korona (so 10 000 Kogi = 1 Korona). The canonical name carries Polish diacritics (Hirońskie, Skeltvorskie) and is the identity.

Matching is case-insensitive and accepts the canonical name, the short name, or any text starting with the stem — so colloquial inflections (koron, talarów, kogów) resolve everywhere a denomination is matched: holdings recognition, @Transfer classification, and new-holding input. On write, the canonical diacritical name is what gets stored. The stem rule is deliberately greedy — an item name beginning with kor/tal/kog classifies as coin, so item names should avoid those prefixes.

The currency-entity shape

A currency holding is a Przedmiot block in nerthus.entities.md, in the standard entity Markdown format owned by the entity model. A hand-authored holding may use the Polish genitive convention {Denomination short} {Owner in genitive} — e.g. Korony Erastera, Talary Gildii Teologów. Holdings the daemon auto-creates (via New-NerthusCurrencyEntity or a transfer to a holder with no holding yet) use the deterministic, non-inflected form {canonical denomination} ({holder}), where the holder is the resolved canonical entity name — e.g. Korony Elanckie (Eraster), Talary Hirońskie (Opat Perrin) — since Polish genitive cannot be generated reliably. Either way the heading is display-only.

## Przedmiot

### Korony Erastera
- @typ: Przedmiot
- @generyczne_nazwy: Korony Elanckie
- @należy_do: Eraster (2024-06:)
- @ilość: 50 (2024-06:)
- @status: Aktywny (2024-06:)

### Korony Skarbca Thuzal
- @typ: Przedmiot
- @generyczne_nazwy: Korony Elanckie
- @należy_do: Thuzal (2025-01:)
- @ilość: 12000 (2025-01:)
- @status: Aktywny (2025-01:)
Tag Meaning Rules
@typ Entity type Always Przedmiot for a coin holding.
@generyczne_nazwy The canonical denomination name Mandatory; this is what marks the entity as currency.
@należy_do The owning holder entity Postać / NPC / Grupa / Gracz / Lokacja (a place that owns a treasury). Mutually exclusive with @lokacja.
@lokacja The place unowned coin lies For dropped/cached/hidden coin with no owner. Mutually exclusive with @należy_do.
@ilość The quantity, temporal One dated row per change — the balance history. Whole coin by convention (the smallest coin is one Kog); decimals are parsed, not rejected.
@status Lifecycle Aktywny / Nieaktywny / Usunięty. Soft-delete only (entity model).

The full closed tag schema, including the temporal range grammar these tags use, is in the tag schema reference. List and report queries match holdings on their projected fields; the transfer engine additionally indexes owned holdings by a folded (case- and diacritic-insensitive) {canonical item}|{owner} key so per-session net deltas land on the right block.

The @Transfer session directive

Coin moves are authored as session-level directives, at the same level as @PU and @Logi (session model). A transfer is symmetric: the daemon applies −N to the source holding and +N to the destination holding, both dated to the session month — the sanctioned effective-date variant of the historical write rule in the tag schema. This is the only sanctioned way coin changes hands in play.

Endpoints are in-game only

Both {source} and {destination} must resolve to an in-game entity, and the daemon enforces the type: Postać, NPC, Grupa, Lokacja, Przedmiot (a named item holder), or Gracz (allowed as a player-level virtual holder). Mapa is the one type rejected. A Narrator is never a transfer endpoint: the narrator who ran the session is read from the session header, never named in a transfer. Out-of-game allocations use paired administrative deltas instead — see The reconciliation invariant.

### 2026-07-01, Eraster rozmawia z Tussalem, Anward

- @Transfer: 10 Korony Elanckie, Eraster -> Lord Tussal
- @Transfer: 50 Talary Hirońskie, Lord Tussal -> Opat Perrin
- @Transfer: Serwis do herbaty Lorda Tussala, Lord Tussal -> Eraster
- @Transfer: 2 Dzbanek herbaty, Lord Tussal -> Eraster

Forms:

Form Syntax
Currency - @Transfer: {amount} {denomination}, {source} -> {destination}
Item (amount = 1) - @Transfer: {item}, {source} -> {destination}
Item (explicit amount) - @Transfer: {amount} {item}, {source} -> {destination}

Currency is just a common item — every holding, coin or not, is a Przedmiot. The daemon classifies each directive from its own syntax:

Directive shape Classified as Effect
50 Talary Hirońskie, A -> B (item resolves as a denomination) currency ±50 on each holder's temporal @ilość; the destination holding is auto-created if absent
2 Dzbanek herbaty, A -> B (non-currency with an amount) stackable item ±2 on per-holder @ilość holdings; destination auto-created
Serwis do herbaty Lorda Tussala, A -> B (non-currency, no amount) unique item its @należy_do is reassigned to B (a new dated owner row; the prior stays as history)

Source and destination go through the Polish-aware resolver, fuzzy matching disabled (ErasteraEraster; see Name resolution). The narrator rejection is checked twice: once literally against the endpoint's raw text before resolution (which catches a narrator who names no entity at all), and again against the canonical name the endpoint resolved to. Both are case-insensitive. The second check is what makes the rule hold: the resolver understands declension, so Anwarda passes a literal comparison and then resolves straight back to the narrator Anward.

An endpoint that resolves to a Mapa is rejected — a map only depicts a place, and the Lokacja it depicts is the holder. Because a Lokacja and its Mapa may share a name, an endpoint landing on a Mapa is re-resolved filtered to -Type Lokacja first, and only rejected if no real place answers to that name.

An endpoint that does not resolve, or a source with no holding of the item, skips that side with a warning — the other side still applies. That skipped debit or credit is exactly the asymmetry reconciliation later surfaces. A unique item that is not an authored entity skips the whole directive (author it first).

The operator-facing authoring recipe is Transfer coin and items.

Applying transfers

@Transfer directives do not move value on parse. Value moves when the Narrator closes the session (sessions model) — that is the normal path, and it is immediate: the coin changed hands at the table, so it changes hands in the books there and then, with no wait for the month.

The same engine is reachable directly as the apply-transfers workflow (Invoke-NerthusSessionTransfers), and the settlement sweep calls it as a catch-up for sessions that were distributed without being closed. All three routes are the one service, dedup-guarded by the session header in .nerthus/state/transfer-ledger.json, so a second application by any route is a no-op (AlreadyApplied = true). The private ledger is machine-local, so an applied session is also echoed to the committed nerthus.ledger.md (- Transfer ### <header> (zastosowano <date>)Architecture); a clone whose private ledger does not know an echoed session refuses to re-apply it with 409 TransferLedgerDrift instead of moving the value twice. Per-holding net deltas are folded first, so one @ilość row dated to the session month is written per affected holding; -WhatIf previews the classified moves and warnings without writing. An applied run records the moves and warnings as a dated entry on the transaction ledger, writes an audit line, and fires transfer:applied. Get-NerthusTransactionLedger reads the ledger, newest first — each entry is { Header, Date, AppliedAt, Moves, Warnings }. The settlement workflow applies sessions neither ledger lists and skips the rest (Settlement).

Physical vs virtual supply

Whether a holding is real or bookkeeping is derived purely from the holder's entity type — no extra tag is stored.

Holder type Class Meaning
Postać Physical Actual coin items in a player character's Margonem equipment
NPC Virtual Narrative-only RP bookkeeping
Grupa Virtual Treasury reserves, guild holdings
Gracz Virtual Player-level (not character-level) coin
Lokacja Virtual A place-held treasury (town vault, temple hoard)
(unresolved holder) Unknown Holder names no entity in the index at all

A retired holder keeps its class. Usunięty gates an entity out of name resolution, but a retired holder still holds its coin, so the class is read from the block the holding's @należy_do names. Classing a retired owner Unknown would silently defeat the very checks below that exist to catch it, and would break the PhysicalSupply + VirtualSupply = Supply identity — Unknown counts toward neither pool. Retiring an owner is not what burns coin; only retiring the holding is.

The split drives orphan handling — physical coin whose owner went inactive must be physically returned to the Koordynatorzy, not just zeroed — and the materialization report, which compares the two pools.

Holding writes

  • New-NerthusCurrencyEntity creates a holding named {canonical denomination} ({holder}) with an initial dated @ilość (default 0) and @status: Aktywny, owned by -Owner or lying at -Location. Input that resolves to no denomination is written verbatim as @generyczne_nazwy, producing a plain (non-coin) item holding.
  • Set-NerthusCurrencyEntity adjusts one holding: exactly one of -Amount (absolute @ilość) or -Delta (signed adjustment), and/or a reassign to exactly one of -Owner or -Location. Every balance, owner, or location change is a dated temporal append, so the holding keeps its history (mechanics in the entity model); the response reports BalanceBefore / BalanceAfter. -Tag/-Value remains a raw single-tag patch (e.g. status).
  • Remove-NerthusCurrencyEntity soft-deletes the holding — this is the burn: from the deletion date the coin leaves Supply, Gini, and materialization, while historical as-of queries still see it. There is no balance check on removal.

All three pass the single write gate and the daemon's write hooks like any other entity write (API reference), and support -WhatIf/-Confirm.

The reconciliation invariant

The conservation rule

Per denomination, total supply is conserved across all holders. Coin enters the world only via New-NerthusCurrencyEntity against a store Grupa and leaves only by soft-delete (burn). Every @Transfer nets to zero by construction; every paired administrative delta must also net to zero.

Out-of-game supply originates in a store: a Grupa that exists to hold coin before it is in play. The daemon creates none and names none — an operator creates them by hand, and the model prescribes only their number and split:

  • one store per Narrator, holding what that Narrator has to give out;
  • one common store for the Council.

The split is what makes the books answerable. A single shared pot cannot say who minted what, how much a given Narrator has handed out, or whether one has spent an allowance twice; per-Narrator stores make each of those a balance read. Naming is the operator's (transfer coin) — the tool keys on @typ: Grupa, never on the name.

Store coin is pre-allocated to in-game holders by paired administrative deltas — never @Transfer. In play, coin reaches a character from an in-game source (a quest-giving NPC, an employer Grupa, or a Lokacja treasury) via @Transfer. The paired deltas are not auto-linked and no check verifies they net to zero (see Open questions); the asymmetries reconciliation does catch come from @Transfer sides skipped at apply time, recorded on the transaction ledger.

Reads and reports

All read-only; wealth math runs in Kogi via Nerthus.EconomicAnalyzer. The holdings list, the single-holding read, the snapshot, and the materialization report honor an as-of date (?activeOn=; exposed as -ActiveOn on Get-NerthusEconomicSnapshot). The currency report and reconciliation always evaluate now. An own-scoped currency.read.own caller sees only their own holdings, and reconciliation (repo-wide) refuses own-only callers — see Permissions.

Get-NerthusCurrencyEntity

Lists all recognized holdings, or reads one by -Name. Each holding is the enriched projection:

Field Meaning
EntityName Currency entity name (Korony Erastera)
Denomination / DenomShort / Tier Canonical name, short, Gold/Silver/Copper
Owner / Location / OwnerType Holder, place, and the holder's entity type
Class / OwnerStatus Physical / Virtual / Unknown; the holder's @status
Balance / Kogi Current @ilość and its Kogi equivalent
Status The holding's own @status

A holding soft-deleted as of the evaluated date is excluded (burned coin).

Get-NerthusCurrencyReport

The same projection filtered by -Owner and/or -Denomination (exact match), evaluated now.

Test-NerthusCurrencyReconciliation

The conservation check (no filter parameters). Returns { Warnings[], WarningCount, Supply, PhysicalSupply, VirtualSupply, EntityCount, CheckedAt } — the three Supply* fields are per-denomination Kogi totals — where each warning is { Check, Severity, Entity, Detail }. Exactly three checks are emitted:

Check Severity Detects
NegativeBalance Error Holding with @ilość < 0
OrphanedCurrency Warning @należy_do points to a Nieaktywny/Usunięty holder
AsymmetricTransaction Warning An applied @Transfer that recorded a skipped side (surfaced from the transaction ledger)

Get-NerthusEconomicSnapshot

Returns { Gini, HolderCount, TopHolders, ActiveOn } from the holdings active on -ActiveOn (default now). Wealth is grouped per holder: owned coin under its owner, unowned coin under its place, otherwise Unowned. The Gini coefficient is computed over the positive per-holder Kogi totals (0.0 = perfectly equal or fewer than two positive holders, 1.0 = one holder owns all). TopHolders is the top 10 by wealth, each row { Owner, WealthKogi, OwnerCategory } with the physical/virtual class.

Get-NerthusEconomicTimeline

Monthly trend points { Month, Gini, HolderCount, Transactions } — each a month-end snapshot. The month set is derived from applied-transfer ledger dates — a month with no applied transfers produces no point (an empty ledger falls back to the last 6 calendar months) — then filtered by -From/-To (YYYY-MM).

Get-NerthusMaterializationReport

The operational view of "how much of the tracked supply actually exists as Margonem items". Returns { Denominations, OrphanedPhysical, ActiveOn }: one row { Denomination, PhysicalKogi, VirtualKogi } per denomination, plus every physical holding whose owner is Nieaktywny/Usunięty as { Entity, Owner, Balance } (flagged for return to the Koordynatorzy).

Cmdlet surface

Holdings: Get-NerthusCurrencyEntity, New-NerthusCurrencyEntity, Set-NerthusCurrencyEntity, Remove-NerthusCurrencyEntity. Reports: Get-NerthusCurrencyReport, Test-NerthusCurrencyReconciliation, Get-NerthusEconomicSnapshot, Get-NerthusEconomicTimeline, Get-NerthusMaterializationReport. Transfers: Invoke-NerthusSessionTransfers, Get-NerthusTransactionLedger.

Routes, parameters, envelopes, and capabilities are in the API reference.

Examples

Create a Narrator's store and fund it (out-of-game, administrative) — the store name is the operator's choice, and the holding is auto-named after it:

New-NerthusEntity -Type Grupa -Name '<the Narrator''s store>'
New-NerthusCurrencyEntity -Denomination 'Korony Elanckie' -Owner '<the Narrator''s store>' -Amount 10000

Pre-allocate coin from that store to an in-game employer by paired administrative deltas:

Set-NerthusCurrencyEntity -Name 'Korony Elanckie (<the Narrator''s store>)' -Delta -500
Set-NerthusCurrencyEntity -Name 'Korony Elanckie (Gildia Teologów)' -Delta 500

In play, an in-game payer hands coin to a character via a session directive (the narrator Anward appears only in the header); closing the session applies it. Tussalowi resolves to the canonical Postać Lord Tussal, so the auto-created holding is named after the resolved owner:

### 2026-07-01, Eraster rozmawia z Tussalem, Anward
- @Transfer: 10 Korony Elanckie, Eraster -> Tussalowi
Invoke-NerthusSessionTransfers -Header '2026-07-01, Eraster rozmawia z Tussalem, Anward'

Check the books:

Get-NerthusCurrencyEntity -Name 'Korony Elanckie (Lord Tussal)'
Get-NerthusCurrencyReport -Owner 'Lord Tussal'
Test-NerthusCurrencyReconciliation
Get-NerthusEconomicSnapshot -ActiveOn 2026-07-31

Open questions / decisions

  • Mint/burn. Decided: no public mint cmdlet — supply enters via New-NerthusCurrencyEntity against a store Grupa, leaves via soft-delete.
  • Administrative-delta drift. Open: paired Set-NerthusCurrencyEntity deltas are not auto-linked, and reconciliation has no supply-drift baseline check to catch a forgotten half.
  • Fractional coin. The convention is whole coin (the smallest coin is one Kog); parsing accepts decimal amounts — open whether to reject them.
  • Display breakdown. Open: all report fields are raw Kogi; the highest-denomination-first breakdown (35 250 → 3 Korony 52 Talary 50 Kogi) is design intent, not implemented.
  • Narrator-endpoint check. Decided: compare both — literally before resolution, and against the canonical name after it. Comparing only literals let a declined form (Anwarda) or an alias resolve back to the narrator and pass; a guard against narrator self-dealing that is bypassed by writing the name in the genitive is worse than no guard, because it reads as enforced.
  • Council-editable denominations. Decided: the table is data, not code — seeded only when currency.json is absent; a new row reshapes all Kogi arithmetic automatically.
  • Report refinements. Planned, not shipped: a per-holding LastChangeDate + @ilość history view, and a StaleBalance reconciliation check.

See also

  • Entity model — the entity Markdown format, index merging, and soft-delete
  • Tag schema — the closed tag table and the temporal range grammar
  • Session model — where @Transfer lines live and the session-header key
  • Name resolution — how transfer endpoints resolve (ErasteraEraster)
  • Configuration — the currency.json data-table file and Council-tunable constants
  • PU model — the other tracked "currency", batch-run alongside reconciliation
  • Permissionscurrency.* capabilities and own-scoping
  • Architecture — the daemon, the write gate, and Nerthus.EconomicAnalyzer
  • API reference — the cmdlet ⇄ route mapping for everything above