# Ashen Realms – Währungssystem Scherben, Taler & Kronen – Implementierungsspezifikation V1
## Zweck
Dieses Dokument beschreibt die technische und fachliche Einführung des allgemeinen Geldsystems von **Ashen Realms**.
Das bisherige Modell mit einer einzelnen sichtbaren Währung „Silber“ wird ersetzt durch ein dreistufiges Währungssystem:
**Scherben → Taler → Kronen**
Das System soll:
- kleine und große Geldbeträge kompakt darstellen,
- zur Welt und Atmosphäre von Ashen Realms passen,
- keine unnötig großen Zahlen in der UI erzeugen,
- technisch weiterhin einfach berechenbar bleiben,
- vollständig serverautoritativ funktionieren,
- mit Händlern, Rufbelohnungen, Quests und späteren Wirtschaftssystemen kompatibel sein,
- klar von Gebietswährungen wie Grenzmarken, Dämmermarken und Siegelbruchstücken getrennt bleiben.
---
# 1. Grundprinzip
Die allgemeine Spielwährung besteht aus drei sichtbaren Einheiten:
| Einheit | Verhältnis |
|---|---:|
| Scherbe | 1 |
| Taler | 100 Scherben |
| Krone | 100 Taler = 10.000 Scherben |
Damit gilt:
```text
100 Scherben = 1 Taler
100 Taler = 1 Krone
1 Krone = 10.000 Scherben
```
Beispiele:
```text
18 Scherben
1 Taler 35 Scherben
8 Taler
1 Krone 42 Taler 65 Scherben
```
---
# 2. Wichtigste technische Entscheidung
## Geld wird intern niemals als drei getrennte Werte gespeichert.
Die Datenbank speichert ausschließlich den Gesamtwert in der kleinsten Einheit:
**Scherben**
Beispiel:
```text
1 Krone 42 Taler 65 Scherben
```
wird intern gespeichert als:
```text
14.265 Scherben
```
Berechnung:
```text
1 × 10.000
+ 42 × 100
+ 65
= 14.265
```
Dadurch bleiben:
- Vergleiche einfach,
- Käufe atomar,
- Belohnungen einfach,
- API-Verträge eindeutig,
- Migrationen übersichtlich,
- Rundungsprobleme ausgeschlossen.
---
# 3. Begriffe
## Allgemeine Währung
Die allgemeine handelbare Währung lautet:
- **Scherben**
- **Taler**
- **Kronen**
Sie wird für normale wirtschaftliche Vorgänge verwendet.
Beispiele:
- Händlerkäufe
- Händlerverkäufe
- Heiltränke
- Taschen
- Basis-Ausrüstung
- Dienstleistungen
- spätere Marktgebühren
- spätere Handelsfunktionen
---
## Gebietswährungen
Gebietswährungen bleiben vollständig separate Systeme.
Beispiele:
- Grenzmarken
- Dämmermarken
- Siegelbruchstücke
Gebietswährungen dürfen niemals automatisch in Scherben umgerechnet werden.
Sie besitzen:
- eigene Balance,
- eigene Händler,
- eigene Freischaltbedingungen,
- eigene Progressionsfunktion.
---
# 4. Abgrenzung zum Rufsystem
Das allgemeine Geldsystem und das Rufsystem arbeiten zusammen, sind aber fachlich getrennt.
Ein Händler oder Questgeber kann bei der Abgabe von Trophäen gleichzeitig vergeben:
```text
Geld
+ regionalen Ruf
+ Weltruhm
```
Beispiel:
```text
5 Raider Insignias abgegeben
Belohnung:
+ 1 Taler 35 Scherben
+ 24 Ruf der Grenzwacht
+ 3 Weltruhm
```
Die drei Belohnungen müssen getrennt berechnet und persistiert werden.
Es gibt keine automatische Formel wie:
```text
1 Ruf = X Scherben
```
Ruf und Geld besitzen unterschiedliche Designfunktionen.
---
# 5. Designziel
Das System soll verhindern, dass normale Preise langfristig aussehen wie:
```text
348.721 Silber
```
Stattdessen:
```text
34 Kronen 87 Taler 21 Scherben
```
Dadurch bleibt die Economy visuell übersichtlich.
---
# 6. Balancing-Grundsatz
Der Vertical Slice soll überwiegend mit:
- Scherben
- wenigen Talern
arbeiten.
Kronen sollen am Anfang selten oder gar nicht auftauchen.
Empfohlene Progression:
## Frühes Spiel
Typische Beträge:
```text
5–90 Scherben
```
## Fortgeschrittenes Tier 1
Typische Beträge:
```text
1–5 Taler
```
## Tier 2
Typische Beträge:
```text
3–25 Taler
```
## Tier 3
Typische Beträge:
```text
15–80 Taler
```
## Spätere Spielphasen
Erst hier werden Kronen zu einer regelmäßigen sichtbaren Einheit.
---
# 7. Umstellung der bisherigen Silberwerte
Die bisherigen Balancing-Dokumente verwenden für den Vertical Slice eine einzelne Währung „Silber“.
Für die erste Migration gilt:
> **1 bisheriges Silber = 1 Scherbe**
Dadurch bleiben sämtliche bestehenden Balancing-Verhältnisse zunächst unverändert.
Beispiele:
| Bisher | Neu |
|---|---|
| 15 Silber | 15 Scherben |
| 50 Silber | 50 Scherben |
| 80 Silber | 80 Scherben |
| 120 Silber | 1 Taler 20 Scherben |
| 180 Silber | 1 Taler 80 Scherben |
| 250 Silber | 2 Taler 50 Scherben |
| 350 Silber | 3 Taler 50 Scherben |
Diese Umstellung ist bewusst rein repräsentativ.
Das eigentliche Economy-Balancing kann später separat angepasst werden.
---
# 8. Aktualisierte Beispielpreise
Die bisherigen Beispielpreise werden zunächst folgendermaßen dargestellt:
| Gegenstand | Neuer Preis |
|---|---:|
| Kleiner Heiltrank | 15 Scherben |
| Basis Tier-1 Item | 50–80 Scherben |
| Basis Tier-2 Item | 1 Taler 20 Scherben – 1 Taler 80 Scherben |
| Basis Tier-3 Item | 2 Taler 50 Scherben – 3 Taler 50 Scherben |
Diese Werte sind nur die direkte Migration des bisherigen Balancings.
Nach Playtests darf die Economy unabhängig davon neu skaliert werden.
---
# 9. Datenmodell
## Character
Der Charakter benötigt einen einzelnen Geldwert.
Empfohlener Feldname:
```ts
moneyShards
```
Alternativ:
```ts
currencyAmount
```
Empfohlen wird jedoch der explizite Name:
```ts
moneyShards
```
Beispiel:
```ts
@Column({
type: 'bigint',
default: 0,
})
moneyShards: string;
```
---
# 10. Warum PostgreSQL BIGINT?
Langfristige Online-Spiele können sehr große Geldmengen erzeugen.
Deshalb sollte kein normales 32-Bit-Integer verwendet werden.
PostgreSQL:
```text
BIGINT
```
TypeORM liefert BIGINT-Werte in JavaScript typischerweise als String.
Dadurch soll im Domain-Code bewusst mit:
```ts
bigint
```
gearbeitet werden.
Beispiel:
```ts
const currentMoney = BigInt(character.moneyShards);
```
---
# 11. Domain-Typ
Optional kann ein eigener Typ eingeführt werden:
```ts
export type MoneyAmount = bigint;
```
Keine Floating-Point-Zahlen verwenden.
Verboten:
```ts
number mit Dezimalstellen
```
Geld besitzt niemals Nachkommastellen.
---
# 12. Zentraler MoneyService
Datei:
```text
apps/api/src/economy/money.service.ts
```
Der Service kapselt sämtliche grundlegenden Geldoperationen.
Beispielinterface:
```ts
@Injectable()
export class MoneyService {
canAfford(current: bigint, price: bigint): boolean;
add(current: bigint, amount: bigint): bigint;
subtract(current: bigint, amount: bigint): bigint;
assertCanAfford(current: bigint, price: bigint): void;
}
```
---
# 13. Validierungsregeln
Für jeden Geldbetrag gilt:
```text
amount >= 0
```
Ein Charakter darf niemals einen negativen Geldbestand besitzen.
Verboten:
```text
-15 Scherben
```
Ein Kauf muss fehlschlagen, wenn:
```text
currentMoney < price
```
Domain-Fehler:
```text
INSUFFICIENT_FUNDS
```
Beispiel:
```json
{
"statusCode": 400,
"code": "INSUFFICIENT_FUNDS",
"message": "You do not have enough money."
}
```
---
# 14. Atomare Geldtransaktionen
Alle Vorgänge, bei denen Geld und ein weiterer Spielzustand verändert werden, müssen atomar erfolgen.
Beispiel Händlerkauf:
```text
Geld abziehen
+ Item hinzufügen
```
muss innerhalb derselben Datenbanktransaktion passieren.
Entweder:
```text
beides erfolgreich
```
oder:
```text
gar nichts
```
Ein Fehler darf niemals zu folgendem Zustand führen:
```text
Geld weg
aber Item nicht erhalten
```
---
# 15. Händlerkauf
Beispiel:
```ts
await dataSource.transaction(async manager => {
const character = await lockCharacter(manager, characterId);
const current = BigInt(character.moneyShards);
const price = BigInt(shopItem.priceShards);
moneyService.assertCanAfford(current, price);
character.moneyShards = (current - price).toString();
await manager.save(character);
await inventoryService.grantItem(
manager,
character.id,
shopItem.itemDefinitionId,
1,
);
});
```
---
# 16. Row Locking
Bei wirtschaftlich relevanten Schreiboperationen soll der Character-Datensatz gesperrt werden.
Empfohlen:
```text
SELECT ... FOR UPDATE
```
bzw. TypeORM pessimistic write lock.
Dadurch werden parallele Käufe verhindert, die denselben Geldbetrag mehrfach ausgeben könnten.
---
# 17. Itempreise
`ItemDefinition` kann weiterhin einen Basispreis besitzen.
Bisher:
```ts
sellPrice
```
Neu:
```ts
sellPriceShards
```
Für kaufbare Angebote sollte der Preis nicht zwingend direkt am Item liegen.
Empfohlen:
```text
ShopOffer
```
mit:
```ts
priceShards
```
Dadurch können verschiedene Händler dasselbe Item zu unterschiedlichen Preisen anbieten.
---
# 18. ShopOffer
Empfohlenes Modell:
```ts
ShopOffer {
id
shopId
itemDefinitionId
priceShards
requiredReputation
requiredGlobalRenown
enabled
}
```
Wichtig:
Der Preis wird vollständig in Scherben gespeichert.
Beispiel:
```text
2 Taler 25 Scherben
```
wird gespeichert als:
```text
225
```
---
# 19. Verkauf von Items
Auch Verkaufserlöse werden in Scherben angegeben.
Beispiel:
```ts
sellPriceShards: 35
```
Der Verkauf:
```text
Item entfernen
+ Geld erhöhen
```
erfolgt atomar.
---
# 20. Trophäen und Händlerabgabe
Für das neue Rufsystem sollte die Abgabe von Trophäen ebenfalls Geld in Scherben vergeben.
Beispiel Reward-Konfiguration:
```ts
{
moneyShards: 135,
regionalReputation: 24,
globalRenown: 3
}
```
Die UI zeigt daraus:
```text
1 Taler 35 Scherben
```
---
# 21. Reward-Modell
Ein generisches Belohnungsmodell kann beispielsweise enthalten:
```ts
export interface RewardDefinition {
moneyShards?: number;
regionalReputation?: number;
globalRenown?: number;
items?: RewardItem[];
currencies?: RewardCurrency[];
}
```
Langfristig kann `moneyShards` intern zu `bigint` werden.
Content-Werte können für den Vertical Slice weiterhin normale Ganzzahlen sein, solange sie beim Domain-Übergang validiert werden.
---
# 22. Gebietswährungen separat halten
Gebietswährungen dürfen nicht in:
```text
moneyShards
```
gespeichert werden.
Empfohlenes separates Modell:
```text
CharacterCurrency
```
Beispiel:
```ts
CharacterCurrency {
characterId
currencyKey
amount
}
```
Beispiele für `currencyKey`:
```text
border-marks
dusk-marks
seal-fragments
```
Dadurch bleiben allgemeines Geld und Progressionswährungen sauber getrennt.
---
# 23. API-Darstellung
Die API sollte Geld immer als kleinste Einheit übertragen.
Beispiel:
```json
{
"moneyShards": "14265"
}
```
Nicht:
```json
{
"crowns": 1,
"talers": 42,
"shards": 65
}
```
Die Aufteilung ist eine Darstellungsaufgabe.
---
# 24. Warum keine aufgeteilten API-Werte?
Folgende Struktur sollte vermieden werden:
```json
{
"crowns": 1,
"talers": 42,
"shards": 65
}
```
Sie erzeugt unnötige Probleme bei:
- Addition,
- Subtraktion,
- Validierung,
- Käufen,
- Rundung,
- Synchronisierung,
- späteren Änderungen des Umrechnungsverhältnisses.
Die Domain besitzt genau einen Wert.
---
# 25. Shared Contract
Im Shared Package:
```ts
export interface MoneyDto {
amountShards: string;
}
```
Oder direkt im Character DTO:
```ts
export interface CharacterDto {
id: string;
name: string;
moneyShards: string;
}
```
---
# 26. Frontend Money Utility
Datei:
```text
apps/web/src/app/shared/money/
```
Empfohlene Funktionen:
```ts
export interface FormattedMoney {
crowns: bigint;
talers: bigint;
shards: bigint;
}
export function splitMoney(amount: bigint): FormattedMoney {
const crowns = amount / 10_000n;
const remainderAfterCrowns = amount % 10_000n;
const talers = remainderAfterCrowns / 100n;
const shards = remainderAfterCrowns % 100n;
return {
crowns,
talers,
shards,
};
}
```
---
# 27. Anzeigeformat
Die Standardanzeige soll leere hohe Einheiten ausblenden.
Beispiele:
```text
18 Scherben
```
nicht:
```text
0 Kronen 0 Taler 18 Scherben
```
---
```text
2 Taler 15 Scherben
```
nicht:
```text
0 Kronen 2 Taler 15 Scherben
```
---
```text
3 Kronen 7 Taler 4 Scherben
```
---
# 28. Nullwerte
Bei exakt null:
```text
0 Scherben
```
Nicht:
```text
–
```
oder leer.
---
# 29. Kurzformat
Für sehr kompakte UI-Bereiche darf ein Kurzformat verwendet werden.
Beispiel:
```text
3 K 42 T 18 S
```
Das Kurzformat ist jedoch nicht die Standarddarstellung.
Standard:
```text
3 Kronen 42 Taler 18 Scherben
```
---
# 30. UI-Komponente
Empfohlene wiederverwendbare Komponente:
```text
MoneyDisplayComponent
```
Input:
```ts
@Input({ required: true })
amountShards!: string | bigint;
```
Optionale Varianten:
```ts
variant:
'full'
| 'compact'
| 'price'
```
---
# 31. Beispiel MoneyDisplayComponent
```html
@if (money.crowns > 0n) {
{{ money.crowns }}
}
@if (money.talers > 0n) {
{{ money.talers }}
}
@if (money.shards > 0n || isZero) {
{{ money.shards }}
}
```
---
# 32. Visuelles Design
Die drei Währungseinheiten sollen eigene Icons erhalten.
## Scherben
Visuell:
- kleines unregelmäßiges Metallstück,
- dunkel,
- abgenutzt,
- niedriger Wert,
- klare Silhouette.
---
## Taler
Visuell:
- geprägte runde Münze,
- dunkles bzw. gealtertes Metall,
- deutlich wertiger als Scherben,
- kein glänzender moderner Coin-Look.
---
## Krone
Visuell:
- hochwertige große Münze oder schwere Prägemünze,
- goldener oder warmer Metallton,
- königliche bzw. alte Prägung,
- deutlich wertvoll.
---
# 33. Designregel für Icons
Die Icons müssen der bestehenden Ashen-Realms-UI folgen:
- Dark Fantasy,
- realistisch bis malerisch,
- keine Cartoon-Optik,
- keine Mobile-Game-Goldmünzen,
- keine grellen Glows,
- klare Lesbarkeit bei kleiner Größe,
- transparente Hintergründe.
---
# 34. Topbar
Die globale Topbar darf das Geld kompakt darstellen.
Empfohlen:
```text
[Krone] 0 [Taler] 3 [Scherbe] 47
```
Wenn eine Einheit null ist, kann sie optional ausgeblendet werden.
Frühes Spiel:
```text
[Taler] 2 [Scherbe] 18
```
---
# 35. Händleransicht
Preise müssen über dieselbe zentrale Komponente dargestellt werden.
Beispiel:
```text
Basic Hide Bag
Preis:
1 Taler 40 Scherben
```
Der Händler darf niemals selbst Geld formatieren.
---
# 36. Tooltips
Bei kompakten Darstellungen darf ein Tooltip den vollständigen Wert zeigen.
Beispiel:
```text
1 Krone 42 Taler 65 Scherben
```
---
# 37. Preisfarben
Geld selbst erhält keine Seltenheitsfarbe.
Bei einem kaufbaren Angebot:
```text
bezahlbar → normale Textfarbe / Goldakzent
nicht bezahlbar → gedämpft / rot markiert
```
Die Einheiten behalten ihr eigenes Icon.
---
# 38. Keine physische Inventarbelegung
Scherben, Taler und Kronen belegen:
```text
keine Inventarslots
```
Sie sind kein normales Item.
Auch wenn sie visuell Münzen darstellen, werden sie als Charakterwert behandelt.
---
# 39. Keine manuellen Wechselvorgänge
Der Spieler muss niemals:
```text
100 Scherben in 1 Taler wechseln
```
oder:
```text
1 Krone aufbrechen
```
Das System rechnet automatisch.
Beispiel:
Spieler besitzt:
```text
1 Krone
```
Kauft Item für:
```text
20 Scherben
```
Danach besitzt er:
```text
99 Taler 80 Scherben
```
Intern:
```text
10.000 - 20 = 9.980
```
---
# 40. Geldbelohnungen aus Kämpfen
Im aktuellen Rufdesign sollen normale Gegner grundsätzlich nicht automatisch allgemeines Geld vergeben.
Stattdessen können Gegner:
- Trophäen,
- Felle,
- Insignien,
- andere verwertbare Materialien
liefern.
Diese werden bei passenden NPCs oder Händlern abgegeben.
Dort entstehen:
```text
Geld
+ Ruf
+ Weltruhm
```
Falls bestimmte Gegner später bewusst direkt Geld tragen sollen, bleibt das technisch möglich.
Beispiel:
```text
Bandit trägt 12 Scherben bei sich
```
Das ist eine Content-Entscheidung und keine technische Einschränkung.
---
# 41. Quests
Quests dürfen Geld direkt vergeben.
Beispiel:
```json
{
"moneyShards": 250
}
```
UI:
```text
2 Taler 50 Scherben
```
---
# 42. Geldquellen
Mögliche Geldquellen:
- Verkauf von Trophäen,
- Trophäenabgabe,
- Questbelohnungen,
- Itemverkauf,
- direkte humanoide Gegnerbeute,
- Events,
- spätere Spielerökonomie.
---
# 43. Geldsenken
Mögliche Geldsenken:
- Heiltränke,
- Taschen,
- Basis-Ausrüstung,
- Händlerangebote,
- Dienstleistungen,
- später Reparatur nur falls jemals eingeführt,
- Handelsgebühren,
- Reise- oder Teleportservices,
- kosmetische Komfortangebote.
Für den Vertical Slice sollen Geldsenken überschaubar bleiben.
---
# 44. Keine künstliche Inflation im Vertical Slice
Für V1 nicht einführen:
- tägliche Geldbelohnungen,
- Login-Gold,
- massive passive Einnahmen,
- exponentielle Questbelohnungen,
- zufällige Geldmultiplikatoren.
Die Economy soll zunächst durch echtes Gameplay entstehen.
---
# 45. Migration der Datenbank
Falls aktuell ein Feld existiert wie:
```text
silver
```
wird es migriert zu:
```text
money_shards
```
Beispielmigration:
```sql
ALTER TABLE "character"
RENAME COLUMN "silver" TO "money_shards";
ALTER TABLE "character"
ALTER COLUMN "money_shards" TYPE BIGINT;
```
Falls `silver` an anderen Tabellen verwendet wird:
```text
sell_price
shop_price
quest_reward
monster_reward
```
sollen diese ebenfalls explizit auf Scherben umbenannt werden.
Beispiele:
```text
sell_price_shards
price_shards
money_reward_shards
```
---
# 46. Keine Legacy-Bezeichnung „silver“
Nach Abschluss der Migration soll die Codebasis keine fachlich relevante Verwendung mehr besitzen von:
```text
silver
silverAmount
silverReward
silverPrice
```
Ausnahme:
historische Migrationen.
---
# 47. Migration von bestehendem Content
Für jeden bestehenden Silberwert gilt initial:
```text
newMoneyShards = oldSilver
```
Beispiel:
```text
oldSilver = 180
```
wird:
```text
moneyShards = 180
```
UI:
```text
1 Taler 80 Scherben
```
---
# 48. Content-Schema
Empfohlene Felder:
## Monster
Falls direkte Geldbelohnungen erlaubt sind:
```ts
moneyMinShards
moneyMaxShards
```
Nicht:
```ts
silverMin
silverMax
```
---
## Item
```ts
sellPriceShards
```
---
## ShopOffer
```ts
priceShards
```
---
## QuestReward
```ts
moneyShards
```
---
## TrophyExchange
```ts
moneyRewardShards
regionalReputationReward
globalRenownReward
```
---
# 49. Serverautorität
Der Client darf niemals an den Server senden:
```json
{
"price": 150
}
```
oder:
```json
{
"reward": 250
}
```
Ein Händlerkauf sendet ausschließlich die fachliche Auswahl.
Beispiel:
```json
{
"shopOfferId": "uuid"
}
```
Der Server lädt anschließend:
```text
Preis
Anforderungen
Item
Rufvoraussetzung
```
aus seinen eigenen Daten.
---
# 50. Beispiel Shop API
## Request
```http
POST /api/shops/:shopId/purchases
```
```json
{
"offerId": "uuid"
}
```
## Response
```json
{
"purchasedItem": {
"itemDefinitionId": "uuid",
"quantity": 1
},
"moneyShards": "842",
"spentShards": "140"
}
```
Frontend:
```text
Verbleibend:
8 Taler 42 Scherben
```
---
# 51. Beispiel Trophy Exchange API
## Request
```http
POST /api/npcs/:npcId/exchanges
```
```json
{
"exchangeId": "raider-insignia-turn-in",
"quantity": 5
}
```
## Server
prüft:
```text
NPC gültig
Exchange gültig
Spieler besitzt benötigte Trophäen
Menge erlaubt
Rufbedingungen erfüllt
```
berechnet:
```text
Geld
Ruf
Weltruhm
```
und führt alles atomar aus.
---
# 52. Beispiel Response
```json
{
"consumed": [
{
"itemKey": "raider-insignia",
"quantity": 5
}
],
"rewards": {
"moneyShards": "135",
"regionalReputation": 24,
"globalRenown": 3
},
"character": {
"moneyShards": "427"
}
}
```
---
# 53. Event-System / WebSocket
Sobald der zentrale WebSocket aus dem geplanten Realtime-Slice vorhanden ist, soll eine Geldänderung als Character-State-Event veröffentlicht werden.
Beispiel:
```ts
{
type: 'CHARACTER_MONEY_CHANGED',
payload: {
moneyShards: '14265',
deltaShards: '135'
}
}
```
Der Client verwendet weiterhin den Serverwert als Wahrheit.
---
# 54. Kein Client-seitiges Hochzählen als Wahrheit
Animationen wie:
```text
+1 Taler 35 Scherben
```
dürfen clientseitig dargestellt werden.
Der tatsächliche neue Kontostand stammt jedoch immer vom Server.
---
# 55. Auditierbarkeit
Für V1 ist kein vollständiges Finanz-Ledger zwingend notwendig.
Die Economy sollte aber so gebaut werden, dass später ein `CharacterMoneyTransaction`-Log ergänzt werden kann.
Mögliche Felder:
```text
id
characterId
amountDeltaShards
balanceAfterShards
reason
referenceType
referenceId
createdAt
```
Beispiele für `reason`:
```text
SHOP_PURCHASE
ITEM_SALE
QUEST_REWARD
TROPHY_EXCHANGE
LOOT
ADMIN_ADJUSTMENT
```
Für den aktuellen Vertical Slice ist dies optional.
---
# 56. Admin-Balancing
Die geplante Admin-/Balancing-Oberfläche sollte Geldbeträge grundsätzlich in Scherben speichern, aber benutzerfreundlich formatieren.
Eingabe darf später beispielsweise sein:
```text
Krone: 0
Taler: 2
Scherben: 35
```
Gespeichert wird:
```text
235
```
---
# 57. Tests – Money Utility
Pflichttests:
```ts
splitMoney(0n)
→ 0 / 0 / 0
splitMoney(18n)
→ 0 / 0 / 18
splitMoney(135n)
→ 0 / 1 / 35
splitMoney(10_000n)
→ 1 / 0 / 0
splitMoney(14_265n)
→ 1 / 42 / 65
```
---
# 58. Tests – MoneyService
Mindestens:
```text
Geld hinzufügen
Geld abziehen
genügend Geld
zu wenig Geld
Nullbetrag
negative Eingaben werden abgelehnt
kein negativer Kontostand
```
---
# 59. Tests – Händlerkauf
Mindestens:
```text
Spieler hat genug Geld
→ Geld wird abgezogen
→ Item wird vergeben
```
```text
Spieler hat zu wenig Geld
→ Fehler
→ Geld bleibt unverändert
→ kein Item
```
```text
Itemgrant schlägt fehl
→ Transaktion rollback
→ Geld bleibt unverändert
```
---
# 60. Tests – parallele Käufe
Ein Integrationstest soll sicherstellen:
Spieler besitzt:
```text
100 Scherben
```
Zwei parallele Requests versuchen jeweils:
```text
80 Scherben
```
auszugeben.
Erwartung:
```text
genau ein Kauf erfolgreich
```
und niemals:
```text
-60 Scherben
```
---
# 61. Tests – Trophäenabgabe
Test:
```text
5 Raider Insignias vorhanden
```
Abgabe:
```text
5
```
Erwartung:
```text
Items entfernt
Geld erhöht
regionaler Ruf erhöht
Weltruhm erhöht
```
Alles in einer Transaktion.
---
# 62. Frontend Tests
Mindestens:
```text
18 → 18 Scherben
135 → 1 Taler 35 Scherben
10000 → 1 Krone
14265 → 1 Krone 42 Taler 65 Scherben
```
Zusätzlich:
- Nullwerte korrekt,
- große Werte korrekt,
- Kaufbutton bei zu wenig Geld deaktiviert oder klar markiert,
- Tooltip zeigt vollständigen Wert.
---
# 63. Internationalisierung
Da der Spielinhalt auf Englisch umgestellt werden soll, sollte die technische Implementierung keine deutsche Währungsbezeichnung fest verdrahten.
Keys:
```text
currency.shard
currency.shard_plural
currency.taler
currency.taler_plural
currency.crown
currency.crown_plural
```
Die deutsche Designbezeichnung bleibt:
```text
Scherbe
Taler
Krone
```
Die endgültigen englischen Ingame-Namen können separat festgelegt werden.
---
# 64. Keine Formatstrings im Backend
Der Backend-Code soll niemals Texte erzeugen wie:
```text
"1 Taler 35 Scherben"
```
Backend:
```text
135
```
Frontend / Localization:
```text
1 Taler 35 Scherben
```
Dadurch bleibt das System übersetzbar.
---
# 65. Empfohlene Modulstruktur
Backend:
```text
apps/api/src/economy/
├── economy.module.ts
├── money.service.ts
├── money.service.spec.ts
├── types/
│ └── money.types.ts
└── errors/
└── insufficient-funds.error.ts
```
Frontend:
```text
apps/web/src/app/shared/money/
├── money-display.component.ts
├── money-display.component.html
├── money-display.component.scss
├── money-display.component.spec.ts
├── money.utils.ts
└── money.utils.spec.ts
```
---
# 66. Shared Contracts
Optional:
```text
packages/shared/src/economy/
├── money.dto.ts
└── index.ts
```
---
# 67. Implementierungsreihenfolge
## Task 1 – Domain und Konstanten
- [ ] Umrechnungswerte definieren.
- [ ] `MoneyAmount`-Typ definieren.
- [ ] MoneyService implementieren.
- [ ] Unit Tests schreiben.
Konstanten:
```ts
export const SHARDS_PER_TALER = 100n;
export const TALERS_PER_CROWN = 100n;
export const SHARDS_PER_CROWN = 10_000n;
```
---
## Task 2 – Character-Migration
- [ ] vorhandenes Silberfeld identifizieren.
- [ ] zu `moneyShards` umbenennen.
- [ ] PostgreSQL-Typ auf BIGINT setzen.
- [ ] bestehenden Wert 1:1 übernehmen.
- [ ] Migration testen.
---
## Task 3 – Content-Migration
Alle bisherigen Felder prüfen:
- [ ] `silverMin`
- [ ] `silverMax`
- [ ] `sellPrice`
- [ ] Shoppreise
- [ ] Questbelohnungen
- [ ] Lootbelohnungen
und auf explizite Scherben-Felder migrieren.
---
## Task 4 – Shop-System
- [ ] `ShopOffer.priceShards`
- [ ] serverautoritativen Kauf implementieren.
- [ ] Character pessimistisch sperren.
- [ ] Geld und Item in einer Transaktion ändern.
- [ ] Fehler `INSUFFICIENT_FUNDS`.
---
## Task 5 – Verkaufssystem
- [ ] `ItemDefinition.sellPriceShards`
- [ ] Verkauf serverseitig validieren.
- [ ] Item entfernen.
- [ ] Geld erhöhen.
- [ ] Transaktion verwenden.
---
## Task 6 – Ruf-/Trophäenabgabe
- [ ] Geldreward in Scherben ergänzen.
- [ ] regionalen Ruf separat berechnen.
- [ ] Weltruhm separat berechnen.
- [ ] Trophäen entfernen.
- [ ] alles atomar persistieren.
---
## Task 7 – Frontend Formatter
- [ ] `splitMoney`
- [ ] Formatierung
- [ ] Unit Tests
- [ ] Lokalisierung vorbereiten.
---
## Task 8 – MoneyDisplayComponent
- [ ] Full-Variante
- [ ] Compact-Variante
- [ ] Price-Variante
- [ ] Icons integrieren.
- [ ] responsive Verhalten prüfen.
---
## Task 9 – Topbar
- [ ] bisherige Silberanzeige entfernen.
- [ ] neue MoneyDisplay-Komponente verwenden.
- [ ] keine separate Businesslogik in Topbar.
---
## Task 10 – Händler
- [ ] Preise mit MoneyDisplay darstellen.
- [ ] Kaufbarkeit anhand Server-/Character-State anzeigen.
- [ ] nach Kauf neuen Server-Kontostand übernehmen.
---
## Task 11 – Reward UI
- [ ] Questbelohnungen
- [ ] Trophäenabgabe
- [ ] Itemverkauf
- [ ] spätere Loot-Zusammenfassung
mit denselben Währungskomponenten darstellen.
---
## Task 12 – Legacy Cleanup
Projektweit suchen nach:
```text
silver
Silver
SILVER
```
und veraltete fachliche Referenzen entfernen.
Historische Migrationen bleiben unverändert.
---
# 68. Definition of Done
Das Währungssystem gilt als implementiert, wenn:
- der Charakter nur einen allgemeinen Geldwert besitzt,
- dieser Wert intern vollständig in Scherben gespeichert wird,
- PostgreSQL BIGINT verwendet wird,
- Scherben, Taler und Kronen nur Darstellungsformen dieses Werts sind,
- 100 Scherben = 1 Taler gilt,
- 100 Taler = 1 Krone gilt,
- Händlerpreise serverseitig geladen werden,
- der Client keine Kaufpreise vorgeben kann,
- Käufe atomar durchgeführt werden,
- negative Geldbestände unmöglich sind,
- parallele Käufe nicht zu Doppel-Ausgaben führen,
- Gebietswährungen vollständig separat bleiben,
- Ruf und Weltruhm separat bleiben,
- die Trophäenabgabe Geld + Ruf + Weltruhm gemeinsam vergeben kann,
- alle bisherigen Silberwerte zunächst 1:1 zu Scherben migriert wurden,
- die UI Geld zentral über eine wiederverwendbare Komponente formatiert,
- keine Geschäftslogik für Kronen/Taler/Scherben in einzelnen Screens dupliziert wird,
- Backend und Datenbank keine formatierten Währungstexte speichern,
- automatisierte Tests für Umrechnung und Käufe existieren.
---
# 69. Bewusst nicht Teil von V1
Noch nicht implementieren:
- Wechselstuben,
- physische Münzstapel im Inventar,
- unterschiedliche Wechselkurse,
- regionale Geldsysteme,
- Inflation-Simulation,
- Bankkonten,
- Kredite,
- Zinsen,
- Spieler-zu-Spieler-Handel,
- Auktionshaus,
- Marktsteuern,
- komplexes Economy-Ledger,
- Währungsverfall.
---
# 70. Zentrale Designregel
> **Scherben, Taler und Kronen sind keine drei Währungen. Sie sind drei Darstellungsstufen derselben allgemeinen Währung.**
Dadurch erhält Ashen Realms die kompakte, klassische Darstellung eines Browser-MMORPGs, ohne technisch unnötige Komplexität einzuführen.
---
# 71. Kurzfassung
Ashen Realms verwendet zukünftig:
```text
100 Scherben = 1 Taler
100 Taler = 1 Krone
```
Die Datenbank speichert ausschließlich:
```text
moneyShards
```
Gebietswährungen bleiben separat.
Ruf und Weltruhm bleiben separat.
Trophäen können bei Händlern oder NPCs in:
```text
Geld
+ regionalen Ruf
+ Weltruhm
```
umgewandelt werden.
Bestehende Silberwerte werden zunächst ohne Rebalancing migriert:
```text
1 Silber = 1 Scherbe
```
Die wichtigste technische Regel lautet:
> **Der Server speichert und berechnet nur Scherben. Die UI macht daraus Scherben, Taler und Kronen.**