docs: m3-befunde in tickets 01, 08 und 14 zurueckgetragen, dreizehnte registry ins glossar

This commit is contained in:
2026-08-12 17:08:27 +02:00
parent 217a7acbbc
commit b25dd1c49a
5 changed files with 97 additions and 3 deletions

View File

@@ -153,6 +153,32 @@ Fähigkeiten. Das Vokabular bleibt hier unverändert.
`damage` · `change_attribute` · `apply_modifier` · `remove_modifier` · `give_item` · `remove_item` · `damage` · `change_attribute` · `apply_modifier` · `remove_modifier` · `give_item` · `remove_item` ·
`start_quest` (08) · `flee` (14) · `run_script` (13) · `give_exp` (15) — **zehn**. `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 ### Bewusst nicht entschieden
Das **Bedingungsvokabular** für `condition`. Es kommt aus Ticket 08, damit Quests und Modifikatoren Das **Bedingungsvokabular** für `condition`. Es kommt aus Ticket 08, damit Quests und Modifikatoren

View File

@@ -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 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. 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 ### Neun Events, feste Liste
| Event | Nutzlast über `source`/`target` hinaus | | 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 Zug**, danach Abbruch mit Warnzeile. Kein Zyklenerkennungs-Graph; der Zähler fängt denselben Fall und
ist eine Zeile. 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 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 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. und jedem Affix-Wechsel gepflegt werden, also genau das Invalidierungsproblem, das 07 vermieden hat.

View File

@@ -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 geminderten multipliziert, ist mathematisch belanglos: `roh × kf × (1−r)` ist dasselbe wie
`roh × (1−r) × kf`. Festzulegen war nur, dass beide multiplikativ *bleiben*. `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: 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 - **Resistenz bei `0.8`** — bei `1.0` wäre ein Wesen immun, und Immunität ist keine Zahl, sondern ein

View File

@@ -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 (das Grid braucht einen mitlaufenden Bildausschnitt, weil Etage 5 breiter ist als ein
80-Spalten-Terminal — das stand in keinem Ticket). 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: **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. - **11**: Der Grenztest in seiner wörtlichen Fassung schlägt beim eigenen Doc-Kommentar an. Muss Kommentarzeilen ausnehmen.

View File

@@ -33,6 +33,11 @@ Klasse, ausgerüstete Gegenstände und befristete Zustände.
Beispiele: „Zwerg: +2 Konstitution", „Eisenschwert: +5 Schaden", „Segen: +10 % Kritische Chance". 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 ## Aktion
Eine einmalige Zustandsänderung. Sie feuert, wirkt, ist vorbei. Quellen sind Angriffe, 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 ## Registry
Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **zwölf**, und Eine benannte Sammlung moddbarer Definitionen, geschlüsselt nach namespaced ID. Es gibt **dreizehn**,
das hier ist die kanonische Liste — sie ist über sieben Tickets gewachsen und stand sonst nirgends und das hier ist die kanonische Liste — sie ist über sieben Tickets und drei Meilensteine gewachsen und
vollständig: stand sonst nirgends vollständig:
| Registry | TOML-Tabelle | | Registry | TOML-Tabelle |
|---|---| |---|---|
@@ -249,6 +254,7 @@ vollständig:
| [Prefabs](#prefab) | `[[prefabs]]` | | [Prefabs](#prefab) | `[[prefabs]]` |
| [Etagen](#etage) | `[[floors]]` | | [Etagen](#etage) | `[[floors]]` |
| [Loot-Tabellen](#loot-tabelle) | `[[loot_tables]]` | | [Loot-Tabellen](#loot-tabelle) | `[[loot_tables]]` |
| Benannte [Modifikatoren](#modifikator) | `[[modifiers]]` |
| Scripts | `[[scripts]]` | | Scripts | `[[scripts]]` |
Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen. Jede dieser Tabellen darf in jeder beliebigen `*.toml` eines [Mods](#mod) stehen.