diff --git a/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md b/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md index 711c3d5..222a458 100644 --- a/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md +++ b/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md @@ -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 diff --git a/.scratch/rustkeep-1.0/issues/07-game-state-baum.md b/.scratch/rustkeep-1.0/issues/07-game-state-baum.md index c7495fd..ee592da 100644 --- a/.scratch/rustkeep-1.0/issues/07-game-state-baum.md +++ b/.scratch/rustkeep-1.0/issues/07-game-state-baum.md @@ -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. diff --git a/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md b/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md index eb0fe0a..25fa3b3 100644 --- a/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md +++ b/.scratch/rustkeep-1.0/issues/11-ui-geruest-screen-modell.md @@ -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 diff --git a/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md b/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md index 043daac..0528d57 100644 --- a/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md +++ b/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md @@ -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 | diff --git a/.scratch/rustkeep-1.0/issues/15-progression-und-levelup.md b/.scratch/rustkeep-1.0/issues/15-progression-und-levelup.md index a591948..d176196 100644 --- a/.scratch/rustkeep-1.0/issues/15-progression-und-levelup.md +++ b/.scratch/rustkeep-1.0/issues/15-progression-und-levelup.md @@ -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. diff --git a/.scratch/rustkeep-1.0/map.md b/.scratch/rustkeep-1.0/map.md index 0d66c02..edd6b04 100644 --- a/.scratch/rustkeep-1.0/map.md +++ b/.scratch/rustkeep-1.0/map.md @@ -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` 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. diff --git a/CONTEXT.md b/CONTEXT.md index ae62999..09029f4 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -187,6 +187,16 @@ Es existiert immer nur **eine** Etage; beim Abstieg wird die vorige verworfen, e Zurückgehen. Gibt es keine Etage mit der nächsten Nummer, ist der Lauf **gewonnen** — die Zahl der Etagen steht nirgends im Code. +## Erfahrung + +Der Fortschrittszähler des Helden. Quellen sind **Tötungen** (Feld `exp_reward` am +[Wesen](#wesen)) und **Quests** (Aktion `give_exp`) — nicht das Betreten einer [Etage](#etage), denn +das belohnte das Durchrennen. + +Ein Aufstieg gibt **genau einen** frei verteilbaren Punkt auf einen der fünf Grundwerte. Es gibt +**keine Obergrenze**: Die Kurve begrenzt sich über steigende Kosten. Erfahrung hat nur der Held — +Gegner tragen zwar ein Level, aber keinen Fortschritt. + ## Initiative Ein Zahlenwert am [Wesen](#wesen), einmal beim Betreten eines Kampfes aus `dexterity` plus Wurf