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
Przedmiotwhose@generyczne_nazwyresolves 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 aLokacja(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 (Erastera → Eraster; 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-NerthusCurrencyEntitycreates a holding named{canonical denomination} ({holder})with an initial dated@ilość(default 0) and@status: Aktywny, owned by-Owneror lying at-Location. Input that resolves to no denomination is written verbatim as@generyczne_nazwy, producing a plain (non-coin) item holding.Set-NerthusCurrencyEntityadjusts one holding: exactly one of-Amount(absolute@ilość) or-Delta(signed adjustment), and/or a reassign to exactly one of-Owneror-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 reportsBalanceBefore/BalanceAfter.-Tag/-Valueremains a raw single-tag patch (e.g. status).Remove-NerthusCurrencyEntitysoft-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
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-NerthusCurrencyEntityagainst a storeGrupa, leaves via soft-delete. - Administrative-delta drift. Open: paired
Set-NerthusCurrencyEntitydeltas 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.jsonis absent; a new row reshapes all Kogi arithmetic automatically. - Report refinements. Planned, not shipped: a per-holding
LastChangeDate+@ilośćhistory view, and aStaleBalancereconciliation 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
@Transferlines live and the session-header key - Name resolution — how transfer endpoints resolve (
Erastera→Eraster) - Configuration — the
currency.jsondata-table file and Council-tunable constants - PU model — the other tracked "currency", batch-run alongside reconciliation
- Permissions —
currency.*capabilities and own-scoping - Architecture — the daemon, the write gate, and
Nerthus.EconomicAnalyzer - API reference — the cmdlet ⇄ route mapping for everything above