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.
10 KiB
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. 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:
- der Aufstieg,
AssignPoints— und das ist die unauffällige:max_healthsteigt erst, wennconstitutiontatsä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.