Toeten gibt Erfahrung (exp_reward stand seit M3 ungelesen an jedem Gegner), give_exp als zwoelfte Aktion fuer spaetere Quests. Aufstieg in einer Schleife am Zugende, ein Punkt je Stufe, keine Obergrenze. Verteilt wird im Charakterbogen — kein zwoelfter Screen. AssignPoints nimmt alle Punkte auf einmal und kostet damit einen Zug. luck wirkt jetzt: eine Chance auf einen Zusatzzug auf jeder Beutetabelle. Drei Befunde: - strength und constitution las niemand, zwei der fuenf verteilbaren Grundwerte waren Fallen. Zwei balance-Knoepfe rechnen sie beim Verteilen um. - Rng::chance(0.0) verbrauchte eine Zahl und verschob den Zufallsstrom. - Cheat::ClearRoom ging am Todesweg vorbei.
180 lines
10 KiB
Markdown
180 lines
10 KiB
Markdown
# 15 — Progression und Level-up
|
|
|
|
Type: grilling
|
|
Status: resolved
|
|
Blocked by: 02
|
|
|
|
## Question
|
|
|
|
Was passiert beim Aufstieg, und wie sieht die Kurve aus?
|
|
|
|
`Player` hat bereits `current_exp` und `next_level_exp`, `Stats::level_up()` deckelt bei 50. Der
|
|
Attributkatalog aus 01 steht. Offen ist, was ein Level überhaupt gibt:
|
|
|
|
- **Was gewährt ein Aufstieg?** Feste Modifikatoren aus der Klasse, frei verteilbare Attributpunkte,
|
|
oder beides? Freie Punkte heißen Zuweisungs-UI und die Gefahr, sich den Charakter zu verbauen.
|
|
- **Ist die Kurve Daten oder Formel?** Eine Tabelle je Level in TOML (moddbar, explizit) oder eine
|
|
Formel mit Parametern (kompakt, weniger schraubbar)?
|
|
- **Bleibt die Obergrenze bei 50**, wenn 1.0 aus fünf Etagen besteht? Passt das zusammen?
|
|
- **Woher kommt Erfahrung?** Nur Tötungen, auch Quests, auch erstmaliges Betreten einer Etage?
|
|
- **Skaliert Rasse oder Klasse mit?** „+2 Konstitution je Level" ist ein Modifikator mit Level-Bezug —
|
|
kann das Vokabular aus 01 das ausdrücken, oder braucht es dafür etwas Neues?
|
|
- **Werden Leben und Ausdauer beim Aufstieg aufgefüllt?** Das ist eine Balancing-Entscheidung mit
|
|
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.
|
|
|
|
### Befunde aus M5
|
|
|
|
**Zwei der fünf verteilbaren Grundwerte waren Fallen.** Das Ticket verteilt Punkte auf `strength`,
|
|
`dexterity`, `constitution`, `charisma` und `luck` — und begründet sauber, warum abgeleitete Größen
|
|
ausgeschlossen bleiben. Nur las `strength` und `constitution` bis M5 **niemand**: `damage` und
|
|
`max_health` sind eigene Attribute ohne Herleitung, `dexterity` steckt in Flucht und Initiative,
|
|
`charisma` seit M4 im Preis, `luck` jetzt in der Beute. Ein Punkt in Stärke hätte nichts getan.
|
|
|
|
Entschieden: `damage_per_strength` und `health_per_constitution` als `[balance]`-Knöpfe, angewandt
|
|
**beim Verteilen** und nicht beim Auslesen. Damit hebt ein Punkt in Konstitution den *Grundwert*
|
|
`max_health` — genau das, was der Satz „`max_health` steigt erst, wenn `constitution` tatsächlich
|
|
erhöht wird, also beim Verteilen" ohnehin voraussetzt. In `Game::attribute` gehört die Umrechnung
|
|
nicht: Dort wäre sie ein Sonderfall im generischen Pfad, dieselbe Begründung, mit der M4 die
|
|
Charisma-Formel in die Preisfunktion gelegt hat.
|
|
|
|
**Von den „zwei Stellen" ist nur eine echt.** Das Ticket nennt Aufstieg *und* `AssignPoints` als
|
|
Stellen, an denen der Lebensstand einem `max_health`-Zuwachs folgt. Im Code ist die erste beweisbar ein
|
|
No-op: Der Aufstieg hebt `base.level` und `unspent_points`, sonst nichts, und `per_level` hat dasselbe
|
|
Ticket ersatzlos gestrichen. Die vorsorgliche Vorher/Nachher-Klammer im Aufstieg ist raus — sie
|
|
suggerierte eine Regel, die es nicht gibt.
|
|
|
|
**`Rng::chance(0.0)` verbrauchte eine Zahl.** Aufgefallen, weil die Glücksregel aus Ticket 16 auch beim
|
|
Erzeugen einer Etage greift (Händlerregal, `luck = 0`) und damit den ganzen Zufallsstrom verschob — ein
|
|
bestehender Kampftest schlug fehl, weil sein Angriff plötzlich danebenging. `chance` kürzt jetzt bei
|
|
0 und 1 ab. Ohne das erzeugte derselbe Seed je nach Glück des Helden eine andere Etage, und die Zusage
|
|
aus Ticket 14 („Seed im Save ⇒ Nachladen würfelt nicht neu") wäre stillschweigend gebrochen gewesen.
|
|
|
|
**`Cheat::ClearRoom` ging am Todesweg vorbei.** Die Werkbank setzte den Lebensstand auf null und ließ
|
|
das Aufräumen am Zugende erledigen — ohne `check_death`, also ohne Todes-Trigger und ohne Erfahrung.
|
|
Genau der Fehler, gegen den ihr eigener Kommentar argumentierte. Sie ruft jetzt `check_death`.
|