Gemeldet als "Ratten vergiften zu 100 %". Dahinter lagen drei Fehler:
1. Creature::from_def parste Attributnamen mit toml::from_str, das eine
ganze TOML-Datei erwartet. Der Aufruf schlug fuer jeden Namen fehl,
der Zweig hiess Err(_) => continue — jeder Gegner hatte seit M3 still
die Standardwerte. Die Riesenratte: 100 Leben statt 14, der
Knochenfuerst war eine grosse Ratte. Kampf gegen den ersten Gegner:
22 Zuege statt 4.
2. attack_hit feuerte bei allen Traegern statt nur beim Angreifer, also
vergiftete die Ratte auch bei eigenen Treffern. EventKind::
bound_to_source benennt die zwei seitengebundenen Ereignisse.
3. Action::Damage wuerfelte die Trefferformel auch bei Selbstschaden —
ein flinker Held wich seinem eigenen Gift aus.
Dazu ein Datenfehler: Das Gift stand in der Aktionsliste von rat_bite,
und Aktionen haben kein chance. Es haengt jetzt am Trigger mit 25 %.
Drei Regressionstests, darunter der, den es seit M3 haette geben muessen:
ein Gegner bekommt die Werte aus seiner Definition.
Gemessen statt geschaetzt — es waren nicht die Zahlen:
- on_create konnte nur give_item, die Startwaffe lag also im Beutel.
Jeder Held schlug mit blossen Haenden zu: Schaden 2 statt 7,7, zehn
Zuege pro Ratte statt zweieinhalb. Neue Aktion equip_item.
- Der Dieb bekam gar keine Waffe. Jetzt ein Dolch (+3 Schaden, +5 Krit).
- critical_chance percent = 15 auf einer Basis von 0 ist ein No-op — die
Signaturzahl des Diebs tat nichts. Basis jetzt 5, Dieb traegt flat 10.
Die Prozent-auf-Null-Falle steht im Glossar unter Modifikator: Sie folgt
aus der Stapelregel aus Ticket 01 und schnappt sonst wieder zu.
Der Held war seit M1 als Zwergenkrieger verdrahtet, waehrend zwei Rassen
und zwei Klassen ungenutzt in der Registry lagen. Der Charakterbau ist
eine Schleife ueber categories_ordered() und options_of() — ein Mod, der
"Talent" mit pick = 2 ergaenzt, bekommt eine dritte Spalte ohne Code.
- CharacterOption bekommt abilities und on_create (Ticket 02, TODO seit M3)
- abilities_of liest jetzt Rasse und Klasse: Krieger traegt Wuchtschlag,
Dieb Hinterhalt, Zwerg Steinsinn
- on_create gibt die Startausruestung als gewoehnliche Aktionen —
starting_hero() schrumpft auf Creature::new(name)
- MainMenu trennt "neuer Held" von "Lauf fortsetzen"
Befund: Das Feld abilities fehlte, obwohl Ticket 15 sein ganzes Argument
darauf stuetzt — eine Klasse konnte nur flache Modifikatoren geben.
Eine Quest IST ein Trigger mit count = N — kein zweiter Mechanismus.
Gezaehlt wird erst ab start_quest, erledigte bleiben mit done = true
stehen, und die Belohnung darf die naechste starten: das ist die Kette.
- fuenfzehnte Registry, Trigger per serde(flatten) eingebettet
- give_item als Aktion, damit eine Belohnung mehr als Erfahrung zahlt
- QuestLog auf q, Fortschritt im Speicherstand, unbekannte IDs verworfen
- eine Funktion entscheidet fuer alle Traeger, wann ein Trigger feuert
Befund: Die Ordnungszahlen im Aktionskatalog widersprechen sich — vier
Tickets zaehlten je vom eigenen Stand. Sie sind raus, CONTEXT.md fuehrt
die Liste.
Autosave beim Betreten einer Etage und beim Beenden, ein Slot, bei Tod
geloescht. Der Save ist der Baum selbst — keine eigenen Save-Structs, die
man zweimal pflegt.
- Kacheln als ASCII-Zeilen wie ein Prefab, nicht 3200 Enum-Eintraege
- atomar geschrieben: tmp plus rename, der einzige Stand ueberlebt einen
Abbruch
- fremdes Schema wird abgelehnt, die Datei bleibt liegen
- fehlende Mods verwerfen Eintraege, nie den Lauf
- Confirm-Screen vor dem Spiel, wenn der Save Eintraege verloren hat
Fuenf Befunde: die Ereigniswarteschlange gehoert nicht in den Save, die
Feldreihenfolge ist Teil des TOML-Formats, Serialize fuer Floor muss von
Hand, Confirm braucht on_no, und die Rueckfrage galt zuerst auch fuer
Mod-Ladewarnungen.
Nach Enter stand unspent_points auf 0, waehrend der Screen den Punkt noch
vorgemerkt hielt — die Anzeige rechnete 0 - 1 auf u32 und stuerzte ab.
Der Screen leert sein Feld, waehrend er das Kommando uebergibt, dazu
saturating_sub an den zwei Lesestellen: unspent_points kann sich auch
unter dem offenen Screen aendern.
Erster UI-Test des Projekts. handle_key ist ohne Terminal aufrufbar, und
sobald ein Screen Zustand haelt, ist die Naht zum Spielzustand eine
Fehlerquelle wie jede andere.
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.
'i' oeffnet denselben Screen wie draussen. `rejects` hat `UseItem` im
Kampf nie verboten — es fehlte nur die Tuer in der UI.
Dabei: Ein Gegenstand traf `Enemy(0)`, auch wenn der schon tot war. Jetzt
der erste lebende. Dass er den Zielcursor ignoriert, steht als TODO — es
faellt erst mit dem ersten Wurfgegenstand auf.
Gegenstandsliste mit Cursor, dazu heilen, absteigen und Raum leeren.
Laeuft ueber ein Kommando Debug(Cheat) statt am Spielkern vorbei — die
Zusage aus Ticket 11, dass Response::Command der einzige Weg nach game/
ist, gilt auch fuer Cheats.
ClearRoom toetet die Gegner, statt die Liste zu leeren: So laeuft der
normale Weg samt Todes-Triggern und "Der Raum ist geraeumt".
use_item und equip entfernten den ganzen Eintrag — ein Schluck aus zwei
Heiltraenken verbrauchte beide. `count` kam in M4 dazu, die drei aelteren
Verbraucher wurden nicht mitgezogen.
Ein Griff fuer alle: take_at(index) nimmt ein Stueck und raeumt den
Eintrag ab, wenn er leer ist. unequip laeuft jetzt durch give, damit ein
abgelegter Stapel wieder verschmilzt.
Dabei aufgefallen: Der Heiltrank hatte nie `actions` — seit M1
verbrauchbar, ohne zu wirken.
Am Zugende legt jeder Gegner fest, was er im naechsten Zug tut; der
Kampf-Screen zeigt es unter dem Lebensbalken. Verbindlich statt Vorschau,
denn nur so entsteht ein Fenster: Wer dem Gegner zwischen Ansage und
Ausfuehrung die Ausdauer nimmt, kostet ihn den Zug.
Zielwahl bekommt einen eigenen Cursor, Tab schaltet zwischen Gegner- und
Faehigkeitsliste um — dieselbe Bedienung wie im Laden. Die Ziffern
springen nur noch aufs Ziel, ausgeloest wird mit Enter.
Die Reihenfolge steht vor dem Zug fest, Flucht leert `enemies` aber
mitten darin — danach zeigten die Indizes ins Leere und ein Gegner, der
nach dem Helden drankam, stuerzte das Spiel ab.
Zwei Zusagen aus Ticket 14, jede fuer sich richtig, nie zusammen geprueft:
alle Kampftests loesten die Flucht am Ende der Reihenfolge aus.
Vierzehnte Registry: NPCs sind keine Wesen — kein Kampfzustand, keine
Initiative, keine Attribute. Ihre Antwortliste ist flach; was erst nach
einer Antwort erscheint, ist eine Option mit einer Bedingung.
- open_shop als elfte Aktion (game/ setzt eine Absicht, die UI schiebt)
- Talk/Choose/Buy/Sell, Talk wirkt auf das eigene Feld wie PickUp
- Preisspanne statt Goldkonto beim Haendler, Charisma in der Preisfunktion
- Warenkorb einmal bei der Etagenerzeugung gezogen
- Stapelregel aus Ticket 07 endlich gebaut: 50-120 Gold war 120 Eintraege
- Dialog- und Shop-Screen, NPC-Symbol im Grid
- data/: [[loot_tables]] und [[affixes]] als Registries; LootEntry mit
guaranteed, nothing, table-in-table, count und affixes
- game/loot.rs: EINE Ziehmaschinerie fuer Gegenstaende und Affixe,
Tiefendeckel gegen wechselseitigen Bezug
- PickUp zieht erst beim Aufheben (Ticket 16) — moeglich nur, weil alle
Haufen dasselbe Symbol zeigen
- UseItem/Equip/Unequip/Drop, Inventar-Screen als Registry-Schleife
- Affixe im Attributpfad, in der Resistenz und in der Trigger-Suche;
item_label stellt Affixnamen voran, item_price multipliziert Faktoren
- Room haelt eine loot_table (nicht je Haufen eine), Prefab-loot
ueberschreibt die Etagentabelle
53 Tests. Offen aus M4: Handel.
condition fragt Wesen, when filtert das Ereignis. Getrennt, weil dieselbe
Bedingungsgrammatik auch beim Auslesen eines Attributs laeuft — dort gibt es
kein Event, und eine Nutzlast-Pruefung in condition waere im Trigger sinnvoll
und am Modifikator immer falsch, ohne dass der Modder es sieht.
Nutzlast zurueck: damage_type und prefab. critical und amount bleiben draussen,
bis jemand sie fordert.
Dazu Item.triggers/actions/abilities und Trigger-Suche in der Ausruestung —
mods/core/items.toml belegt es mit einem Dornenpanzer, der NUR Feuer
reflektiert.
45 Tests.
Der Dungeon hat jetzt Gegner, und man kann sterben.
- game/action.rs: sieben Schadenstypen, fuenf der zehn Aktionen, Target
- game/condition.rs: Bedingungsgrammatik (Liste = UND, not je Eintrag)
- game/event.rs: zwoelf Events, Trigger als vierte Wirkungsform
- game/combat.rs: Treffer- und Schadensformel mit drei Klemmbereichen
- Game::apply, drain_events (FIFO), Initiative, Rundenablauf aus Ticket 14
- data/: abilities-, creatures- und modifiers-Registry, [balance] feldweise
- ui/: Combat-Screen mit Faehigkeitsmenue und Sperrgrund
Drei Befunde:
- Die Trefferformel war toter Code — jeder Angriff traf. Nur der Compiler
hat es gemeldet ('hit_chance is never used'). Test haelt sie am Leben.
- Die Warteschlange war ein Stack (pop statt pop_front), obwohl Ticket 08
ausdruecklich 'haengt es hinten an' sagt. Jetzt VecDeque.
- Benannte Modifikatoren (core:poisoned) hatten keine Registry — Ticket 01
laesst apply_modifier per ID referenzieren, aber keine der zwoelf
definierte sie. Das ist die dreizehnte.
43 Tests.
Der handgebaute Testraum ist weg; Etagen entstehen aus Daten.
- game/rng.rs: xorshift64*, Seed im Game (Ticket 14)
- game/generate.rs: freie Platzierung, Pflichtraeume zuerst, Kette
i->i-1 fuer strukturelle Erreichbarkeit, acht Orientierungen
- data/: [[prefabs]] und [[floors]] als neunte und zehnte Registry,
Ladepruefungen fuer rechteckige Layouts und Pflichtraum-Flaeche
- Descend plus Siegbedingung: keine [[floors]] mit der naechsten Nummer
heisst gewonnen — die Zahl 5 steht nirgends im Code
- GameOver-Screen, mitlaufender Bildausschnitt im Grid
Befund: Korridore stanzten 2340 Loecher in gezeichnete Waende, weil sie
AUF der Tuer starteten. Erst Schritt nach aussen, dann L-Form, sonst
Wegsuche ueber den freien Raum — die kann eine Raumwand nicht beruehren.
Der Test zaehlt jetzt null.
21 Tests.
Ersetzt src/ vollstaendig: print_box, include_str! und enum ItemType fallen,
wie Ticket 01-03 es vorgezeichnet haben.
- data/: Mod-Lader mit zwei Suchpfaden, selbstheilender load_order.toml,
drei Phasen und zweistufiger Deserialisierung (03/04)
- game/: Attributkatalog, Creature, Game::attribute ueber mehrere Quellen,
Command mit Outcome (01/07/11/12)
- ui/: Screen-Stack mit Response, Grid-Widget, Press-Filter (06/11/12)
- mods/core/: echte TOML-Dateien auf der Platte statt include_str!
10 Tests, darunter der Borrow-Beweis fuer Registries-neben-Game, den
Ticket 12 nicht fuehren konnte.