diff --git a/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md b/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md index 222a458..28078a1 100644 --- a/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md +++ b/.scratch/rustkeep-1.0/issues/01-effekt-vokabular.md @@ -153,6 +153,32 @@ Fähigkeiten. Das Vokabular bleibt hier unverändert. `damage` · `change_attribute` · `apply_modifier` · `remove_modifier` · `give_item` · `remove_item` · `start_quest` (08) · `flee` (14) · `run_script` (13) · `give_exp` (15) — **zehn**. +### Nachtrag aus M3: benannte Modifikatoren brauchen eine Registry + +Oben steht `{ action = "apply_modifier", modifier = "core:poisoned", … }` — eine Aktion referenziert +einen Modifikator **per ID**. Aufgefallen beim Bauen von M3: **Keine der zwölf Registries definiert +benannte Modifikatoren.** `core:poisoned` hatte kein Zuhause. + +Das ist die **dreizehnte Registry**, `[[modifiers]]`: + +```toml +[[modifiers]] +id = "poisoned" +name = "Vergiftet" +symbol = "☠" +turns = 4 +attributes = [ { attribute = "damage", percent = -15 } ] +triggers = [ { on = "turn_started", + actions = [ { action = "damage", amount = 2, damage_type = "poison", target = "self" } ] } ] +``` + +Das innere Feld heißt bewusst `attributes` und nicht `modifiers` — ein Modifikator, der Modifikatoren +enthält, liest sich wie ein Fehler. Und `effects` wäre der Begriff, den dieses Ticket ausdrücklich +verworfen hat. + +Ein Modifikator ist damit ein **Bündel**: mehrere Umrechnungen plus Trigger plus Dauer. Gift senkt den +Schaden *und* tickt — das wäre mit einer einzelnen Umrechnung nicht ausdrückbar. + ### Bewusst nicht entschieden Das **Bedingungsvokabular** für `condition`. Es kommt aus Ticket 08, damit Quests und Modifikatoren diff --git a/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md b/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md index 9869367..f751b02 100644 --- a/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md +++ b/.scratch/rustkeep-1.0/issues/08-event-trigger-system.md @@ -71,6 +71,28 @@ Level ≥ 3" wird zu zwei Einträgen dupliziert — oder der Modder hängt beide Bedingungen werden an **zwei** Stellen ausgewertet: beim Feuern eines Triggers und beim Auslesen eines Attributs (bedingte Modifikatoren aus 01). Letzteres erzwingt den Nachtrag an Ticket 07, siehe unten. +### Nachträge aus M3 + +Beim Bauen kamen zwei Prüfungen dazu, die oben fehlten: + +- **`health_below`** mit einem Anteil (0.0–1.0). Die `ai`-Listen aus 14 brauchen „unter 30 % Leben", und + das ist mit `attribute` nicht ausdrückbar: `health` ist **Zustand**, kein Attribut (01), und ein + *Verhältnis* lässt sich mit `at_least`/`below` auf einem absoluten Wert nicht formulieren. +- **`chance`** am Trigger. Dieses Ticket nennt „Flammendes Schwert: 20 % Chance, Brennen auszulösen" als + Motivbeispiel, hat aber kein Feld dafür festgelegt. Ohne es bräuchte jeder Wahrscheinlichkeitseffekt + ein Script — und Scripting liegt laut 13 hinter 1.0. + +### Offene Lücke aus M3: ein Trigger sieht sein Event nicht + +Die Events oben tragen Nutzlast (Schadenstyp, Prefab-ID, Menge) — **die Bedingungsgrammatik kann sie +nicht prüfen.** `Check` sieht nur Wesen, nie das Ereignis. Damit ist „Dornenpanzer reflektiert nur +Feuer" oder „nur bei kritischen Treffern" derzeit nicht formulierbar. + +In M3 wurden `Event.damage_type` und `Event.label` deshalb gestrichen: Felder, die niemand auslesen +kann, sind Kommentare in Feldform. Was fehlt, ist eine Prüfungsfamilie über die Nutzlast — etwa +`{ check = "damage_type", value = "fire" }`. Zu entscheiden, bevor Affixe und Gegenstands-Trigger +kommen (M4), denn dort werden solche Effekte erwartet. + ### Neun Events, feste Liste | Event | Nutzlast über `source`/`target` hinaus | @@ -144,6 +166,12 @@ Gegen Zyklen — Trigger A löst B aus, B löst A aus — ein schlichter Zähler Zug**, danach Abbruch mit Warnzeile. Kein Zyklenerkennungs-Graph; der Zähler fängt denselben Fall und ist eine Zeile. +**Befund aus M3: die Warteschlange war ein Stack.** Die erste Umsetzung nahm `Vec::pop()`, also LIFO — +ein Folge-Event drängelte sich vor die schon wartenden. Genau das sollte die Warteschlange verhindern +(„hängt es hinten an, statt den Stack zu vertiefen"). Jetzt `VecDeque` mit `pop_front`. Die Formulierung +oben war richtig, die Umsetzung nicht, und **kein Test hat es gemerkt** — Reihenfolge fällt erst auf, +wenn zwei Trigger um dasselbe Ziel konkurrieren. + Trigger werden bei jedem Event **gesucht, nicht indiziert** — dieselbe Entscheidung wie „kein Attribut-Cache" in 07 und aus demselben Grund: Ein Index müsste bei jedem An- und Ablegen, jedem Loot und jedem Affix-Wechsel gepflegt werden, also genau das Invalidierungsproblem, das 07 vermieden hat. 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 06a1cb7..a2a2988 100644 --- a/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md +++ b/.scratch/rustkeep-1.0/issues/14-kampfformeln-und-rundenablauf.md @@ -102,6 +102,15 @@ p_flucht = clamp(0.5 + (dexterity(Held) - dexterity(schnellster Gegner)) * FLUCH geminderten multipliziert, ist mathematisch belanglos: `roh × kf × (1−r)` ist dasselbe wie `roh × (1−r) × kf`. Festzulegen war nur, dass beide multiplikativ *bleiben*. +**Befund aus M3: die Trefferformel war toter Code.** `hit_chance` existierte, war getestet — und wurde +nie aufgerufen. **Jeder Angriff traf.** Gemeldet hat es nur der Compiler +(`warning: function 'hit_chance' is never used`); kein Test schlug an, weil alle Tests die Formel +*direkt* prüften statt über einen Angriff. + +Lehre für die Formeln unten: Eine Formel, die nur von ihrem eigenen Unit-Test aufgerufen wird, ist +nicht verdrahtet. Der Test, der das jetzt abfängt, führt 200 Angriffe gegen einen absurd flinken +Gegner und verlangt, dass mindestens einer verfehlt *und* mindestens einer trifft. + Drei Klemmbereiche, jeder mit Grund: - **Resistenz bei `0.8`** — bei `1.0` wäre ein Wesen immun, und Immunität ist keine Zahl, sondern ein diff --git a/.scratch/rustkeep-1.0/map.md b/.scratch/rustkeep-1.0/map.md index 89c3705..8afa2a9 100644 --- a/.scratch/rustkeep-1.0/map.md +++ b/.scratch/rustkeep-1.0/map.md @@ -66,6 +66,31 @@ Wandfelder; zwei L-Varianten reichen nicht, es braucht eine Wegsuche über den f (das Grid braucht einen mitlaufenden Bildausschnitt, weil Etage 5 breiter ist als ein 80-Spalten-Terminal — das stand in keinem Ticket). +**M3 — Kampf, Events und Trigger** (`feat/m3-kampf`, 43 Tests) + +| Zusage | Aus | Befund | +|---|---|---| +| Trigger werden **gesucht, nicht indiziert** | 08 | **bestätigt** — die letzte offene Papierzusage. Gift tickt über einen `turn_started`-Trigger, ohne Script | +| Warteschlange statt Rekursion | 08 | **erst nach Korrektur** — die Umsetzung war ein Stack | +| Zug = Spieler plus alle Gegner, Initiative am Wesen | 14 | **bestätigt**, Gleichstand geht an den Spieler | +| Tote erst am Zugende entfernen | 14 | **bestätigt** | +| Gesperrte Fähigkeit ⇒ `Rejected`, kein Zug | 14 | **bestätigt** | +| Resistenz typ-indiziert, bei 0.8 gedeckelt | 01/14 | **bestätigt**, Verwundbarkeit bei −1.0 ausdrückbar | +| Krit-Reihenfolge ist belanglos | 14 | **bestätigt**, direkt nachgerechnet | +| Gegner gehören zum Raum, verschoben nicht kopiert | 10 | **bestätigt** | +| Trefferformel | 14 | **war toter Code** — jeder Angriff traf, siehe unten | + +Drei Befunde nachgetragen: **14** (die Trefferformel wurde nie aufgerufen — nur der Compiler hat es +gemeldet, weil alle Tests sie *direkt* prüften statt über einen Angriff), **08** (die Warteschlange war +`Vec::pop`, also LIFO — die Formulierung war richtig, die Umsetzung nicht, und kein Test hat es +gemerkt) und **01** (benannte Modifikatoren wie `core:poisoned` hatten keine Registry; jetzt die +**dreizehnte**). + +Dazu zwei Ergänzungen am Bedingungsvokabular (`health_below`, `chance` am Trigger) und **eine offene +Lücke**: Ein Trigger kann die Nutzlast des Events, das ihn ausgelöst hat, **nicht prüfen** — `Check` +sieht nur Wesen. „Dornenpanzer reflektiert nur Feuer" ist damit nicht formulierbar. Zu entscheiden vor +M4, weil dort Affix- und Gegenstands-Trigger kommen. + **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. diff --git a/CONTEXT.md b/CONTEXT.md index bde618a..fe1aa3e 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -33,6 +33,11 @@ Klasse, ausgerüstete Gegenstände und befristete Zustände. Beispiele: „Zwerg: +2 Konstitution", „Eisenschwert: +5 Schaden", „Segen: +10 % Kritische Chance". +Ein **benannter** Modifikator (`core:poisoned`) ist ein Bündel: mehrere Umrechnungen, eigene +[Trigger](#trigger) und eine Dauer in [Zügen](#zug). Er lebt in der Registry `[[modifiers]]` und wird +von der [Aktion](#aktion) `apply_modifier` aufgelegt. Er **wohnt beim Träger** — stirbt der, verschwindet +er mit ihm. + ## Aktion Eine einmalige Zustandsänderung. Sie feuert, wirkt, ist vorbei. Quellen sind Angriffe, @@ -232,9 +237,9 @@ Es gibt **keinen Seltenheitsbegriff**: Abstufungen sind eigene Einträge mit kle ## Registry -Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **zwölf**, und -das hier ist die kanonische Liste — sie ist über sieben Tickets gewachsen und stand sonst nirgends -vollständig: +Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **dreizehn**, +und das hier ist die kanonische Liste — sie ist über sieben Tickets und drei Meilensteine gewachsen und +stand sonst nirgends vollständig: | Registry | TOML-Tabelle | |---|---| @@ -249,6 +254,7 @@ vollständig: | [Prefabs](#prefab) | `[[prefabs]]` | | [Etagen](#etage) | `[[floors]]` | | [Loot-Tabellen](#loot-tabelle) | `[[loot_tables]]` | +| Benannte [Modifikatoren](#modifikator) | `[[modifiers]]` | | Scripts | `[[scripts]]` | Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen.