docs: ticket 15 (progression) aufloesen, nachtraege an 01 07 11 14, erfahrung ins glossar

This commit is contained in:
2026-08-12 13:45:41 +02:00
parent 380400611e
commit 5faa5d6165
7 changed files with 170 additions and 2 deletions

View File

@@ -138,10 +138,20 @@ eine Aktion, die sie ausführt.
überall verwendbar, wo eine Aktion stehen darf, und braucht keine eigenen Platzierungsregeln. Es gibt
Aktionen zurück, die in die Warteschlange aus 08 wandern — der Schreibpfad bleibt also genau einer.
### Nachtrag aus Ticket 15: `give_exp`
`{ action = "give_exp", amount = 50, target = "self" }` als **zehnte Aktion**, für Questbelohnungen.
Erfahrung aus Tötungen läuft dagegen über das Feld `exp_reward` an der `creatures`-Registry — die
Kurzform für einen Todes-Trigger, den sonst jede Gegnerdefinition mitschleppen müsste.
Ticket 15 hat außerdem geprüft, ob ein Modifikator „je Level" skalieren können muss (`per_level`).
**Antwort: nein** — Charakteroptionen geben flache Boni, und was sie dauerhaft relevant hält, sind
Fähigkeiten. Das Vokabular bleibt hier unverändert.
### Der Katalog
`damage` · `change_attribute` · `apply_modifier` · `remove_modifier` · `give_item` · `remove_item` ·
`start_quest` (08) · `flee` (14) · `run_script` (13) — **neun**.
`start_quest` (08) · `flee` (14) · `run_script` (13) · `give_exp` (15) — **zehn**.
### Bewusst nicht entschieden

View File

@@ -177,6 +177,15 @@ Zwei neue Felder am `Creature`:
Dazu der RNG-Seed als Feld in `Game`, damit Nachladen nicht neu würfelt.
Ticket 15 beantwortet außerdem die Frage, die 07 offen an es weitergereicht hatte — wo der
EXP-Fortschritt wohnt: **`current_exp` und `unspent_points: u32` gehören an `Game`**, nicht an
`Creature`. `base.level` bleibt am Wesen, weil Gegner echte Level haben und die Etagen-Skalierung sie
liest; Erfahrung und unverteilte Punkte hat dagegen ausschließlich der Held, und ein Feld, das für
99 % der Exemplare bedeutungslos ist, schriebe der Save für jeden Gegner mit.
`base.level` ist dabei die **einzige** Quelle für Punkte; `attribute(wer, "level")` darf durch
Modifikatoren höher ausfallen, ohne welche zu schenken.
Und die Antwort auf die Frage, die 07 offen an 14 gereicht hatte: **Tote Gegner werden am Ende von
`Game::execute` entfernt**, nach dem Leerarbeiten der Event-Warteschlange. Innerhalb eines Zuges
bleiben Indizes stabil.

View File

@@ -59,6 +59,12 @@ hätte es vergessen.
`Unequip(SlotId)` · `Drop(usize, u32)` · `PickUp(usize)` · `Descend` · `Talk(NpcId)` ·
`Buy(ItemId, u32)` · `Sell(usize, u32)` · `Wait`
*Nachtrag aus Ticket 15:* **`AssignPoints`** kommt als dreizehnter Command dazu — ein Aufstieg gibt
einen frei verteilbaren Attributpunkt, verteilt wird im `CharacterSheet`. Bewusst **ein** Command mit
allen Punkten auf einmal statt einer je Punkt: Drei Stufen wären sonst drei Züge, in denen ein
Gift-Debuff tickt, während man ein Menü bedient. Dasselbe Muster wie `Buy(item, 10)`, und damit ohne
Ausnahme von der Zugregel.
Weder Angriff noch Flucht sind **eigene Varianten** — beides sind Fähigkeiten:
`UseAbility("core:attack", ziel)` und `UseAbility("core:flee", …)`. Ticket 02 hat Fähigkeiten als
Registry festgelegt, also gehören sie dorthin, und ein Mod kann beide ersetzen: eine Klasse, die

View File

@@ -206,6 +206,13 @@ nicht.
Standardwerte liegen in der Engine, nicht in `core`. Dann ist ein Mod, der einen Knopf dreht, eine
Zeile lang.
**Nachtrag aus Ticket 15:** Zwei weitere Knöpfe kommen dazu — `exp_base` und `exp_exponent`, die die
Erfahrungskurve bilden. Eine Tabelle je Level wäre die Alternative gewesen; zwei Zahlen schlagen
fünfzig Zeilen. Dazu bekommt die `creatures`-Registry ein Feld `exp_reward`.
Ausdrücklich **nicht** dazugekommen ist `points_per_level`: Ein Aufstieg gibt genau einen Punkt, das
ist eine Regel und kein Stellknopf.
### Vier Nachträge an geschlossene Tickets
| Ticket | Nachtrag |

View File

@@ -1,7 +1,7 @@
# 15 — Progression und Level-up
Type: grilling
Status: open
Status: resolved
Blocked by: 02
## Question
@@ -23,3 +23,126 @@ Attributkatalog aus 01 steht. Offen ist, was ein Level überhaupt gibt:
Folgen für die Etagen-Länge.
Ergebnis: Aufstiegsregel, Kurvenform (Daten oder Formel), Erfahrungsquellen und die Obergrenze.
## Answer
Begriffe im [Glossar](../../../CONTEXT.md). Dieses Ticket entscheidet **Formen und Regeln** — die
konkreten Zahlen bleiben beim Balancing, das ein spielbares Spiel voraussetzt.
### Ein Aufstieg gibt genau einen Punkt
Frei verteilbar auf die **fünf Grundwerte**: `strength` · `dexterity` · `constitution` · `charisma` ·
`luck`. Abgeleitete Größen wie `damage` oder `hit_chance` bleiben ausgeschlossen — Punkte direkt
dorthin zu setzen umginge das ganze Modifikatorsystem aus 01.
**`current_exp` und `unspent_points` leben an `Game`, nicht am `Creature`.** Ticket 07 hatte die
Platzierung ausdrücklich hierher delegiert; `base.level` bleibt am Wesen (Gegner haben echte Level, die
Etagen-Skalierung liest sie), aber Erfahrung und unverteilte Punkte hat ausschließlich der Held. Ein
Feld, das für 99 % der Exemplare bedeutungslos ist, wäre ein Kommentar in Feldform — und der Save
schriebe es für jeden Gegner mit. Beide Werte werden ohnehin nur beim Aufstieg und in `AssignPoints`
angefasst, und beides läuft über `Game`.
Verteilt wird im `CharacterSheet` aus Ticket 11, kein zwölfter Screen. **Kein Umverteilen** — ein
Permadeath-Lauf ist kurz und endet endgültig, eine Respec-Mechanik löst ein Problem, das dabei gar
nicht entsteht.
`AssignPoints` ist **ein** Command mit allen Punkten auf einmal und kostet damit **einen** Zug. Ein
Command je Punkt hieße bei drei Stufen drei Züge, in denen ein Gift-Debuff tickt, während man ein Menü
bedient; eine Ausnahme von der Zugregel wollte Ticket 11 ausdrücklich vermeiden. Dasselbe Muster wie
`Buy(item, 10)`.
### Keine Obergrenze
`Stats::level_up()`s `clamp(1, 50)` fällt ersatzlos. Fünfzig Stufen über fünf Etagen wären
bedeutungslos, und eine harte Grenze wäre der einzige Teil der Progression, der einen späteren
**Endlosmodus** blockierte.
Die Kurve begrenzt sich selbst über steigende Kosten: `exp_base` und `exp_exponent` als zwei Felder in
`[balance]` aus Ticket 14 — zwei Zahlen statt fünfzig Tabellenzeilen. Eine Tabelle ließe einen Modder
Level 13 zum Sprung machen; falls das je gewünscht ist, ist sie der Ausbau.
### Erfahrung aus Tötungen und Quests
`exp_reward` als Feld an der `creatures`-Registry aus 14, plus **`give_exp` als zehnte Aktion** für
Questbelohnungen.
Das sind zwei Wege zum selben Ziel, gerechtfertigt nach demselben Muster, mit dem Ticket 01 `armor` als
Kurzschreibweise für Resistenz zugelassen hat: `exp_reward` ist die Kurzform für einen Todes-Trigger,
den sonst **jede einzelne** Gegnerdefinition mitschleppen müsste — samt einer `is_self`-Prüfung, die es
gar nicht gibt.
**Keine Erfahrung fürs Betreten einer Etage.** Das belohnte das Durchrennen; wer Räume auslässt, soll
schwächer ankommen — das ist die Spannung, von der ein Abstieg lebt.
Geprüft wird am Ende von `Game::execute`, nach dem Leerarbeiten der Warteschlange aus 08 — dann wirkt
eine Questbelohnung noch im selben Zug. Reicht die Erfahrung für mehrere Stufen, wird in einer
**Schleife** aufgestiegen; drei Stufen geben drei Punkte. Eine Stufe pro Ereignis erzeugte sonst die
Frage, wann der Rest nachgeholt wird, plus einen Zustand „aufstiegsbereit", den niemand bestellt hat.
### Die Zuwachs-Regel bleibt eng
Steigt `max_health` durch Progression um 10, steigt der Lebensstand um 10 mit — **aber nur durch
Progression.**
Ticket 01 hat davor ausdrücklich gewarnt: *„Ein Modifikator wirkt auf `max_health`, eine Aktion ändert
den Lebensstand. Die Trennung muss scharf bleiben, sonst heilt ein Ring beim Anlegen."* Eine allgemeine
Regel „max ändert sich → Stand ändert sich mit" bräche das sofort.
Genau **zwei** Stellen erhöhen den Stand um den Zuwachs, beide im Spielkern benannt statt aus einer
allgemeinen Regel gefolgert:
1. der Aufstieg,
2. `AssignPoints` — und das ist die unauffällige: `max_health` steigt erst, wenn `constitution`
tatsächlich erhöht wird, also beim Verteilen und nicht schon beim Aufstieg.
Ausrüstung nie. Der Unterschied ist die Dauerhaftigkeit: Ein Ring kommt und geht, ein Punkt in
Konstitution bleibt.
### Rasse und Klasse bleiben flach
Das beantwortet die Skalierungsfrage aus dem Ticketkörper mit „gar nicht": **`per_level` als Nachtrag
an 01 entfällt ersatzlos.** Charakteroptionen geben einen einmaligen, flachen Bonus.
Dass „Zwerg: +2 Konstitution" auf Level 30 nur noch Rauschen ist, ist gewollt. Was Charakteroptionen
dauerhaft trägt, sind **Fähigkeiten** — die veralten nicht. Ticket 02 hat sie als „Modifikatoren *und*
Fähigkeiten" definiert, und der interessante Teil ist die zweite Hälfte: Ein Magier bleibt ein Magier,
weil er zaubern kann, nicht weil er +2 auf etwas hat.
### `level` darf modifiziert werden
`Creature.base.level` steigt beim Aufstieg und ist die **einzige** Quelle für Punkte.
`attribute(wer, "level")` darf durch Modifikatoren höher ausfallen und wird von Bedingungen („Level ≥
3"), Formeln und Etagen-Skalierung gelesen.
Ein Ring mit `+1 level` macht den Helden also für alles, was Level *liest*, eine Stufe stärker — ohne
einen Punkt zu schenken. Dieselbe Trennung wie zwischen `max_health` und Lebensstand. Modifikatoren auf
`level` zu verbieten wäre die Bevormundung, die Ticket 02 abgeräumt hat, und nähme einem Modder ein
hübsches Item, das keinen Schaden anrichtet.
### Endlosrunner — neu aufgetaucht, bewusst hinter 1.0
In dieser Sitzung kam auf, dass das Fernziel ein **Endlosrunner** ist. Das berührt drei geschlossene
Entscheidungen: die Siegbedingung aus 10, `GameOver { outcome: Won }` aus 14, und die
`floors`-Registry, die einen Eintrag *je Nummer* hat — Etage 500 bräuchte eine Definition.
**1.0 bleibt endlich.** Die endliche Fassung ist vollständig durchentschieden, und ein Endlosmodus ist
additiv: ein Feld `repeat = true` an der letzten Etage plus eine Skalierungsformel. Umgekehrt hieße es,
10 und 16 gegen ein Zielbild zu bauen, das noch niemand gespielt hat.
Die fehlende Level-Obergrenze ist der Teil, der davon **jetzt schon** profitiert — sie wäre der einzige
Blocker gewesen.
### Vier Nachträge an geschlossene Tickets
| Ticket | Nachtrag |
|---|---|
| **01** | `give_exp` als **zehnte** Aktion |
| **07** | `current_exp` und `unspent_points: u32` an **`Game`**, nicht am `Creature` — die Platzierung, die 07 hierher delegiert hatte |
| **11** | `AssignPoints` als **dreizehnter** Command |
| **14** | `exp_base` und `exp_exponent` in `[balance]`, dazu `exp_reward` an der `creatures`-Registry |
### Auflage an Ticket 16
Die Beute-Skalierung darf **nicht gegen fünf feste Etagen fest verdrahtet** werden — der Endlosmodus
kommt später, und eine Skalierung, die nur bis Etage 5 denkt, wäre dann ein Umbau statt einer
Erweiterung.

View File

@@ -77,6 +77,7 @@ auch über Ticket 13.
- [12 — Prototyp: ratatui-Durchstich](issues/12-prototyp-ratatui-durchstich.md) ([#13](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/13)) — **Das Modell aus 11 traegt.** Primaerquelle: Branch `prototyp/12-ratatui-durchstich`, `cargo run --example durchstich`. Bestaetigt: `mod game` bleibt ratatui-frei ohne Disziplin, Grid als eigenes `impl Widget` ueber `buf[(x,y)].set_symbol(…)`, `Vec<Screen>` mit Overlay-Flag fuer Rendern *und* Auto-Schliessen, `Response` statt Stack-Mutation (kein Borrow-Aerger). **Der Windows-Fallstrick ist jetzt eine Zahl**: `Tasten 14 → Press 7 → Zuege 7` — ohne Filter kostete jeder Schritt zwei Zuege und damit doppelte Spielzeit. Zwei Korrekturen: 11 sagte „alle Commands kosten einen Zug", richtig ist **alle ausgefuehrten** (`Outcome { TurnPassed, Rejected(&str) }`, Grund reist mit zurueck); und `Command` muss `Clone` sein, damit `Confirm` es herausgeben kann. Ungetestet blieb `Registries` neben `Game` — der Durchstich laedt keine.
- [13 — Script-Engine wählen](issues/13-script-engine-waehlen.md) ([#14](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/14)) — **Rhai**, aber die Wahl ist dank der Leitplanke aus 05 nur noch eine *Implementierungs*-Entscheidung: Scripts lesen ueber eine Handvoll Funktionen (gespiegelt an der Bedingungsgrammatik aus 08 — sie sehen nichts, was eine Bedingung nicht auch saehe) und schreiben ausschliesslich ueber Aktionen. Begruendung unbequem, aber ehrlich — **fuer ein unveroeffentlichtes Terminal-Roguelike schreibt niemand Mods**, der Lua-Vorsprung zahlt sich erst mit einer Gemeinde aus. Mods gelten als **vertrauenswuerdig** (wie bei Factorio, Minecraft, Skyrim), gehaertet wird gegen Unfaelle: acht Engine-Schalter, gegen die Doku geprueft; `set_max_operations` ausdruecklich **nicht** in `[balance]`, sonst hebt ein Mod sein eigenes Limit auf. `rand()` laeuft durch den Seed aus 14, sonst wuerfelten ausgerechnet Scripts beim Laden neu. `run_script` als neunte Aktion, Scripts als **elfte Registry**. Risiken beobachtet mit benanntem Ausstiegssignal; `smartstring` ist ein INFO-Advisory, kein Ausstiegsgrund. **Stufe (c) wandert hinter 1.0** — eine Script-Engine zu bauen, bevor jemand an eine Vokabular-Luecke gestossen ist, baut die Antwort vor der Frage.
- [14 — Kampfformeln und Rundenablauf](issues/14-kampfformeln-und-rundenablauf.md) ([#15](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/15)) — **Ein Zug = Spielerhandlung plus alle Gegnerreaktionen**, sonst tickte ein Debuff bei drei Gegnern viermal. Initiative als `i32` **am Wesen** statt als Reihenfolgeliste (die waere eine zweite Indexsammlung mit demselben Ungueltigkeitsproblem); tote Gegner erst am Ende von `execute`, nach dem Leeren der Warteschlange. Trefferformel und Schadenskette mit drei Klemmbereichen — `0.8` bei Resistenz, weil `1.0` Immunitaet und damit ein Spielabbruch waere. **Die Krit-Reihenfolge-Frage loest sich auf**: multiplikativ ist kommutativ. Eigener xorshift, **Seed im Save** — Nachladen wuerfelt nicht neu. Faehigkeiten kosten Ausdauer, Abklingzeit in Zuegen, gesperrt ⇒ `Rejected` ohne Zug. **Achte Registry: Wesen** — die Luecke lag seit 02 offen; `ai` als geordnete Bedingungsliste. `[balance]` verschmilzt **feldweise**, ausdrueckliche Ausnahme zu 03 (festes Schema). Vier Nachtraege: `scale` und `flee` an 01, drei Felder an 07, zwei Events an 08, `GameOver` an 11.
- [15 — Progression und Level-up](issues/15-progression-und-levelup.md) ([#16](https://gitlab.hive-brain.de/MDietermann/rustkeep/issues/16)) — Ein Aufstieg gibt **genau einen** frei verteilbaren Punkt auf die fuenf Grundwerte; `AssignPoints` ist **ein** Command mit allen Punkten (drei Stufen waeren sonst drei Zuege im Menue). **Keine Obergrenze** — `clamp(1, 50)` faellt, die Kurve begrenzt sich ueber steigende Kosten (`exp_base`, `exp_exponent` in `[balance]`). Erfahrung aus Toetungen (`exp_reward` am Wesen) und Quests (`give_exp`, zehnte Aktion), **nicht** fuers Betreten einer Etage — das belohnte das Durchrennen. **Die Zuwachs-Regel bleibt eng**: nur Aufstieg und `AssignPoints` heben den Lebensstand mit `max_health`, Ausruestung nie — sonst „heilt ein Ring beim Anlegen" (01). Rasse und Klasse bleiben **flach**, `per_level` entfaellt ersatzlos; was sie traegt, sind Faehigkeiten. `level` darf modifiziert werden, aber Punkte haengen an `base.level` — ein `+1 level`-Ring schenkt keinen. Vier Nachtraege: `give_exp` an 01, `current_exp`/`unspent_points` an **`Game`** statt ans Wesen (07 hatte die Platzierung hierher delegiert), `AssignPoints` an 11, zwei Balance-Knoepfe plus `exp_reward` an 14.
## Not yet specified
@@ -86,6 +87,8 @@ auch über Ticket 13.
- **Inventar-UX** — Gewicht oder Kapazität (`carry_capacity` ist seit 01 ein Attribut ohne Regel), Sortierung, Vergleichsansicht. 11 hat nur den Screen und die Commands festgelegt, nicht was darin steht.
- **Mod-Verteilung** — durch 03 auf „Ordner ins Profil entpacken" geschrumpft, ohne Versionsprüfung. Offen ist nur noch, ob 1.0 mehr braucht.
- **Endlosmodus** — das erklärte Fernziel (aufgekommen in Ticket 15). 1.0 bleibt endlich; endlos ist **additiv**: ein Feld `repeat = true` an der letzten Etage plus eine Skalierungsformel. Berührt drei getroffene Entscheidungen — die Siegbedingung (10), `GameOver { outcome: Won }` (14) und die `floors`-Registry, die einen Eintrag *je Nummer* hat. Die fehlende Level-Obergrenze aus 15 ist bereits darauf ausgelegt.
## Out of scope
- **Story-Modul** — kommt später als eigenes Modul, linear statt prozedural, ebenfalls moddbar. Eigene Map, wenn 1.0 steht.