feat(progression): Erfahrung, Aufstieg und Punkteverteilung

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.
This commit is contained in:
2026-08-12 21:15:25 +02:00
parent b072e5ade3
commit b19492bb51
12 changed files with 421 additions and 45 deletions

View File

@@ -146,3 +146,34 @@ Blocker gewesen.
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`.

View File

@@ -132,6 +132,30 @@ die Tickets zurückgetragen:
Der Zugablauf aus **14** hat damit einen siebten Schritt, `Creature` ein Feld `intent`, und die
Sperrgründe, die `ability_block` ohnehin berechnete, haben einen zweiten Nutzen bekommen.
**M5 — Progression** (`feat/m5-progression`, 64 Tests)
| Zusage | Aus | Befund |
|---|---|---|
| `current_exp`/`unspent_points` an `Game`, nicht am `Creature` | 15 | **bestätigt** — der Save schreibt sie nicht für jeden Gegner mit |
| `exp_reward` als Kurzform für einen Todes-Trigger | 15 | **bestätigt**, stand seit M3 ungelesen an vier Gegnern |
| Aufstieg in einer Schleife, drei Stufen = drei Punkte | 15 | **bestätigt** |
| Zwei Zahlen statt fünfzig Tabellenzeilen für die Kurve | 15 | **bestätigt** — `exp_base × level^exp_exponent` |
| `AssignPoints` als **ein** Kommando, **ein** Zug | 15 | **bestätigt** |
| Verteilen im Charakterbogen, kein zwölfter Screen | 11/15 | **bestätigt** |
| Punkte nur auf die fünf Grundwerte | 15 | **zwei davon waren Fallen** — siehe unten |
| Lebensstand folgt dem Zuwachs an **zwei** Stellen | 15 | **es ist eine** — der Aufstieg ändert `max_health` gar nicht |
| `luck` = eine Chance auf einen Zusatzzug, auf jeder Tabelle | 16 | **bestätigt**, wirkt dreifach aus einer Formel |
Vier Befunde nachgetragen: **15** (`strength` und `constitution` las niemand — zwei der fünf
Wahlmöglichkeiten taten nichts), **15** (von den zwei Zuwachs-Stellen ist eine beweisbar ein No-op),
**14/16** (`Rng::chance(0.0)` verbrauchte eine Zahl und verschob den Zufallsstrom — derselbe Seed
erzeugte je nach Glück des Helden eine andere Etage) und **11** (`Cheat::ClearRoom` ging am Todesweg
vorbei, also ohne Trigger und ohne Erfahrung).
Der dritte ist der interessanteste: Gefunden hat ihn ein **bestehender** Kampftest, dessen Angriff
plötzlich danebenging. Kein neuer Test, keine Überlegung — eine Zusage aus Ticket 14 wäre sonst
stillschweigend gebrochen gewesen.
**Drei Befunde aus M1, die nur der Compiler liefern konnte** — alle in die Tickets zurückgetragen:
- **11**: Der Grenztest in seiner wörtlichen Fassung schlägt beim eigenen Doc-Kommentar an. Muss Kommentarzeilen ausnehmen.