28 KiB
Map: rustkeep 1.0
Gitea-Spiegel: Issue #1.
Diese Datei ist die kanonische Quelle — bei Abweichung gilt .scratch/.
Gitea hat keine nativen Parent-Child- oder Blocking-Beziehungen, deshalb steht beides dort als Konvention im Body. Zuordnung Ticket → Gitea-Issue:
| Ticket | Gitea | Ticket | Gitea |
|---|---|---|---|
| 01 Effekt-Vokabular | #2 | 06 Research ratatui | #7 |
| 02 Typ-Registry | #3 | 07 Game-State-Baum | #8 |
| 03 Mod-Ladepipeline | #4 | 08 Event-System | #9 |
| 04 Fehlerstrategie | #5 | 09 Save-Schema | #10 |
| 05 Research Script-Engine | #6 | 10 Raum-Prefab-Format | #11 |
| 11 UI-Gerüst | #12 | 12 Prototyp ratatui | #13 |
| 13 Script-Engine wählen | #14 | 14 Kampfformeln | #15 |
| 15 Progression | #16 | 16 Loot-Tabellen | #17 |
Destination — erreicht am 2026-08-12
Alle sechzehn Tickets sind geschlossen. Damit ist die Entscheidungsphase vorbei und die Umsetzung läuft — Stand siehe „Verification" weiter unten.
Sechs Meilensteine statt neuer Tickets: M1 vertikaler Durchstich · M2 Etagengenerierung und Prefabs · M3 Kampf mit Events und Triggern · M4 Inventar, Ausrüstung, Affixe, Handel · M5 Quests und Save/Laden · M6 Inhalt für fünf Etagen und Balancing. Die Map hat sich für Entscheidungen gelohnt; dieselbe Zeremonie über Implementierungsaufgaben zu stülpen wäre Prozess um seiner selbst willen.
1.0 heißt: 5 Etagen mit Boss, Solo-Held mit Rasse und Klasse, Grid-Erkundung mit Menü-Kampf, Inventar und Ausrüstung, Quests, NPCs, Handel, Speichern/Laden, Mod-Support auf den Stufen (a) und (b).
Stufe (c) — Scripting — ist entschieden, aber nicht Teil von 1.0: Engine (Rhai) und Schnittstelle stehen, gebaut wird sie, wenn ein Modder an eine Lücke im Vokabular stößt (13).
Verification — was der Compiler bestätigt hat
Sechzehn Tickets Papier, davon hatte bis M1 nur das Screen-Modell (12) Kontakt zu einem Compiler. Diese Liste wächst mit jedem Meilenstein und ist der einzige Ort, an dem steht, welche Entscheidung gehalten hat.
M1 — vertikaler Durchstich (feat/m1-durchstich, 10 Tests)
| Zusage | Aus | Befund |
|---|---|---|
Registries neben Game, disjunkte Borrows |
07 | bestätigt — game.execute(cmd, ®) und game.attribute(…, ®) nebeneinander. Ticket 12 konnte das nicht prüfen, weil der Durchstich keine Registries lud |
| Zweistufige Deserialisierung, Eintrag als Fehlereinheit | 04 | bestätigt — ein kaputtes Feld tötet seinen Eintrag, die Nachbarn überleben |
Game::attribute sammelt aus mehreren Quellen |
07 | bestätigt — Basis + Optionen + Ausrüstung, Stapelregel aus 01 stimmt |
game/ bleibt ratatui-frei |
11 | bestätigt — aber der Test dafür musste nachgebessert werden |
| Trigger-Suche, Event-Warteschlange | 08 | ungeprüft — es gibt weder Trigger noch Events. M3 |
M2 — Etagengenerierung (feat/m2-etagen, 21 Tests)
| Zusage | Aus | Befund |
|---|---|---|
Kette i → i−1 garantiert Erreichbarkeit strukturell |
10 | bestätigt über 29 Seeds, per Flutfüllung gezählt |
| Pflichträume zuerst ist die Garantie | 10 | bestätigt über 39 Seeds — Eingang, Treppe, Händler immer da |
| Türen explizit, keine Löcher in gezeichneten Wänden | 10 | erst nach zwei Korrekturen — siehe unten |
| Seed im Save ⇒ Nachladen würfelt nicht neu | 14 | bestätigt — gleicher Seed, gleiche Etage |
| Sieg über das Fehlen der nächsten Etage | 10 | bestätigt — keine Fünf im Code |
Zwei Befunde nachgetragen: 10 (der Korridor startete auf der Tür und grub die Wandreihe auf — 2340 Wandfelder; zwei L-Varianten reichen nicht, es braucht eine Wegsuche über den freien Raum) und 11 (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.
- 03: Die Referenzseite war nie entschieden. Präfixiert wird nur
id; Verweise stehen voll qualifiziert. Asymmetrisch — zu klären, bevor der erste Fremdmod existiert. - 04:
Spannedzeigt auf den Eintragskopf, nicht auf das schuldige Feld. Deckt sich mit der Zusage, ist aber gut zu wissen.
Notes
Domain: Rust-Terminal-Roguelike, moddbar. Repo: MDietermann/rustkeep.
Das Glossar liegt in CONTEXT.md im Repo-Wurzelverzeichnis — kanonische Begriffe,
keine Implementierung. Jede Session, die einen Begriff schärft, schreibt ihn dort fest.
Jede Session konsultiert: ponytail (dauerhaft aktiv), /grilling und /domain-modeling für
Grilling-Tickets, /research für Research-Tickets, /prototype für Prototype-Tickets.
Feststehende Rahmenentscheidungen aus der Charter-Session:
| Thema | Entscheidung |
|---|---|
| Motivation | Rust lernen und Spiel bauen — Lern-Entscheidungen bekommen eigene Tickets, wo sie echte Architekturfragen sind |
| Kernschleife | Grid-Bewegung zum Erkunden, Kampf im Menü (Dungeon Master / Etrian Odyssey) |
| Held | Solo, kein Party-System |
| Rendering | ratatui + crossterm; print_box fliegt raus, sobald ratatui steht |
| Etagen | Hybrid — Raum-Prefabs in TOML, prozedural zusammengesetzt |
| Daten | Alles TOML, zur Laufzeit geladen. include_str! und enum ItemType fallen |
| Mod-IDs | Namespaced (core:iron_sword), Präfix aus dem Manifest und automatisch vorangestellt. Ladereihenfolge aus einer Spielerliste, nicht aus dem Manifest (03). Das Kernspiel ist selbst ein Mod |
| Mod-Stufen | Daten → Typen → Scripting, in dieser Reihenfolge. Scripting setzt auf der Typ-Registry auf. 13: Rhai gewählt, Umsetzung aber hinter 1.0 — gebaut wird sie, wenn das Vokabular an eine Grenze stößt |
| Effekte | Ein generisches Effekt-System mit Wirkung auf Stats. Rasse, Klasse, Items, Statuseffekte, Quest-Belohnungen und später Scripts nutzen dasselbe Vokabular — dieses Vokabular ist die Mod-API |
| Quests | Bedingung + Belohnung als Daten, generisches Event-/Trigger-System |
| Weltzustand | Game-Ownership-Baum, Verweise über String-IDs (keine Indizes, 07). Registries liegt daneben, nicht darin. Kein ECS |
| Fehler | Result statt panic! — kaputte Mod-TOMLs sind der Normalfall, nicht der Ausnahmefall |
| Save | TOML, Mod-Liste im Header, fehlender Mod = Warnung und Verwerfen, kein harter Abbruch |
Leitplanke aus Ticket 05: Scripts reden ausschließlich über das Effekt-Vokabular, nie direkt mit dem Game-State. Solange das gilt, ist die Script-Engine austauschbar — Ticket 01 entscheidet damit indirekt auch über Ticket 13.
Decisions so far
- 01 — Effekt-Vokabular (#2) — Zwei Begriffe statt einem: Modifikator (rechnet ein Attribut um, solange er existiert) und Aktion (ändert Zustand einmalig), getrennte Maschinen und getrennte TOML-Arrays. Fester Attributkatalog ohne Namensraum;
max_healthist Attribut, der Lebensstand ist Zustand. Sechs Aktionen,damageeigenständig wegen der Minderungskette. Stapeln(Basis + Σ flach) × (1 + Σ prozentual). Eine Uhr: der Zug. Sieben feste Schadenstypen, ein Minderungsmechanismus (resistance),armornur Kurzschreibweise. Auren wohnen beim Träger. Bedingungsvokabular bewusst an Ticket 08 delegiert. Begriffe im Glossar. - 02 — Typ-Registry statt Enums (#3) — Es gibt keinen Item-Typ mehr:
enum ItemTypevermischte Slot, Verhalten und Kategorie und entfällt ersatzlos zugunsten der Felderslot,stackable,consumed_on_use.ItemWeaponundItemArmorfallen weg, ein Gegenstand ist einheitlich. Fähigkeit kommt als dritter Begriff neben Modifikator und Aktion dazu (Angebot statt Wirkung). Fünf moddbare Registries; Slots und sogar Charakteroption-Kategorien sind Inhalt, mitpick = N. Startausrüstung iston_createals gewöhnliche Aktionsliste.value→base_price,shop_price→price_modifier. - 03 — Mod-Ladepipeline und Manifest (#4) — Zwei Suchpfade:
mods/neben der Binary (enthältcore/) unddata_dir()/rustkeep/modsüber die Cratedirectories; bei gleicher ID gewinnt das Profil, mit Warnzeile.coreliegt als echter Mod-Ordner auf der Platte,include_str!entfällt.manifest.tomlmit vier Feldern (id,name,version,depends) — TOML statt YAML, weilserde_yamlund der Forkserde_ymlbeide archiviert sind. Ladereihenfolge aus einer selbstheilenden Spielerlisteload_order.toml;dependssortiert nicht, sondern prüft, und eine Verletzung ist harter Startfehler. Jede*.tomlim Mod wird eingelesen, jede darf jede Registry-Tabelle enthalten. Präfix wird automatisch vorangestellt, Überschreiben überoverrides = "core:iron_sword"am Eintrag — der landet nur unter der überschriebenen ID. Drei Phasen (Manifeste → Einlesen → Auflösen), dadurch keine Vorwärtsreferenzen; Fehlereinheit ist der Eintrag. Kein Nachladen zur Laufzeit. - 04 — Fehlerstrategie für kaputte Mods (#5) — Syntax ist ein Datei-Fehler, alles Semantische ein Eintrags-Fehler; damit 03s Versprechen hält, wird zweistufig deserialisiert (
Vec<Spanned<toml::Value>>, dann Eintrag für Eintrag), sonst reißt ein Tippfehler die ganzeitems.tomlmit. Genau drei harte Fehler:dependsfehlend,dependsverletzt,corenicht gefunden — stderr undexit(1).coreist sonst kein Sonderfall; abgesichert wird es durch einen#[test], dermods/core/mit null Warnungen lädt. Warnungen sammeln sich inVec<String>und landen inlogs/load-<zeitstempel>.log(letzte zehn), plus eine Zeile auf dem Startbildschirm — kein Report-Screen. Meldungmeinmod/items.toml:42: core:iron_sword — <message>, Mod-ID vorne, keine Spalte. Rust: handgeschriebenesenum LoadErrormitimpl Displaystattthiserror/anyhow,load_mods() -> Result<(Registries, Vec<String>), LoadError>, Kette endet vormainmiteprintln!undexit(1). Panic-Grenze ist die Datenherkunft: TOML istResult, eigener Code darf panicken. - 05 — Research: Script-Engine (#6) — Empfehlung Rhai wegen Fehlermeldungen mit Zeile und Spalte, billiger Typ-Brücke, AST-Hot-Reload und Build ohne
cc. Gemessen: mlua liest unterStdLib::ALL_SAFEperio.openechte Dateien — „safe" heißt dort speichersicher, nicht fähigkeitsbeschränkt. Lua gewinnt bei der Modder-Vertrautheit; WASM scheidet für 1.0 aus. Kernfolge: reden Scripts nur über das Effekt-Vokabular, ist die Engine-Wahl umkehrbar. Entscheidung offen in Ticket 13. - 06 — Research: ratatui-App-Architektur (#7) — Screen-Stack über zentralem
Game-State statt Elm oder Component-Trait;game/bleibt ratatui-frei; Popups perClearauf Sub-Rectstatt Widget-Stack; blockierendesevent::read()ohne Tick; Dungeon-Grid ist Eigenbau, es gibt kein Widget dafür; ratatui 0.30.2 mit MSRV 1.88;KeyEventKind::Press-Filter unter Windows Pflicht. Voller Bericht in research/06. - 07 — Game-State-Baum (#8) —
Registrieslebt nebenGame, nicht darin: als Feld kollidierte&mut game.playermit&game.registriesbei jedem Methodenaufruf. Verweise perString-ID, keine Indizes (die hängen an der Ladereihenfolge). EinCreaturefür Held und Gegner, aber zwei Felderplayerundenemiesstatt einerVec— Solo-Held heißt, „Gegner greift Gegner an" existiert nicht, und zwei Felder sind ein disjunkter Borrow. EinFloor, kein Backtracking; Händler müssen deshalb auf jeder Etage vorkommen können. Instanzzustand kommt:ItemInstance { def, affixes }, und Affixe sind die sechste Registry — dadurch bleibt der Save eine ID-Liste und der Preisbase_price × Π price_factor. Stapeln bei gleicherdefund gleicheraffixes.enum CreatureRef, dazu je ein EingangGame::applyundGame::attribute— Letzteres aufGame, weil Auren beim Träger wohnen und einCreatureseine Attribute allein gar nicht ausrechnen kann. Kein Attribut-Cache. - 08 — Event- und Trigger-System (#9) — Der Trigger ist die vierte Wirkungsform neben Modifikator, Aktion und Fähigkeit:
{ on, condition, actions }darf überall stehen, womodifiersundactionsstehen — sonst entstünde dieselbe Maschine ein zweites Mal in 14 alson_hit. Eine Bedingungsgrammatik für Trigger und Modifikatoren: getaggtes Enum, Liste ist UND,notje Eintrag, kein ODER (zwei Einträge oder ein gemeinsames Tag). Neun feste Events ohne Namensraum, jedes mitsource/target;attack_hitunddamage_takenbleiben getrennt, weil der eine beim Angreifer und der andere beim Getroffenen hängt. Im Trigger istselfimmer der Träger. Warteschlange statt synchron — synchrones Feuern wäre ein Borrow-Fehler (überself.questsiterieren, währendself.applyläuft) — mit Zähler bei 100 Events pro Zug statt Zyklengraph. Trigger werden gesucht, nicht indiziert. Quest = Trigger mitcount, siebte Registry, erledigte bleiben markiert stehen. Zwei Nachträge an geschlossene Tickets:start_questals siebte Aktion in 01, undagainst: Option<CreatureRef>plusevents: Vec<Event>in 07. - 09 — Save-Schema (#10) — Layout wird gespeichert, nicht gewuerfelt: Regenerieren aus dem Seed braeuchte dieselben Prefabs und denselben Generator-Code, sonst steht der Held nach einem Mod-Update in einer Wand — und der Raumzustand muss ohnehin mit, der Seed spart also nur 3 KB ASCII. Ein Slot, Autosave bei Etagenbetreten und Beenden, Permadeath (geloescht erst beim
GameOver-Screen, nicht beim Tod). Der Save ist derGame-Baum per serde-Ableitung, eine Ausnahme fuers Kachelgitter. Schema-Version ist das einzige ausgewertete Header-Feld; abweichend ⇒ ablehnen, Datei bleibt liegen (Migration vor dem ersten ausgelieferten Save waere Code ohne Problem). Fehlende Mods: verwerfen und warnen, nie ablehnen — auch bei der Klasse. Mod-Version wird nur protokolliert, das ist die ehrliche Bilanz des Felds aus 03. Warnungen in dieselbe Sitzungsdatei wie der Mod-Ladelauf, plus Rueckfrage vor dem Betreten (der Spieler will vielleicht nachinstallieren statt weiterzuspielen). Atomar geschrieben ueber.tmpundfs::rename— die eine Stelle, an der bewusst nicht lazy entschieden wurde. Nachtraege:core:attack/core:fleeimplizit an 02,Confirm { on_yes: Box<Response> }an 11. - 10 — Raum-Prefab-Format (#11) — Freie Platzierung mit L-Korridoren; verbunden wird Raum
imit Raumi−1, weil eine Kette Erreichbarkeit strukturell garantiert statt sie nachtraeglich zu pruefen. Prefab als ASCII-Block mit Markern im Block, Tueren explizit (sonst stanzt der Generator Loecher in gezeichnete Waende); Rotation und Spiegelung erlaubt,rotate = falseals Opt-out. Einesagt „hier steht ein Gegner", nicht welcher — das entscheidet die Etage, sonst waere jedes Prefab an eine Schwierigkeitsstufe gebunden.[[prefabs]]und[[floors]]sind die neunte und zehnte Registry; Auswahl durchgehend ueber Tags, Pflichtraeume zuerst platziert (das ist die Garantie), Flaechenpruefung beim Laden. Kampf beginnt beiroom_entered, Gegner gehoeren zum Raum — das erspart Pfadsuche, Sichtlinien und Verfolgung.Floormitrooms,player_room: Option<usize>(Korridor ist ein echter Zustand). Siegbedingung erstmals definiert: kein[[floors]]mit der naechsten Nummer = gewonnen, die Zahl „5" steht nirgends im Code. Nachtraege:room_enteredan 08 (zwoelf Events),GameOver { outcome }an 14. - 11 — UI-Gerüst und Screen-Modell (#12) — „Aktion" hiess zwei Dinge:
Actionbleibt das TOML-Effektvokabular aus 01,Commandwird das UI-Kommando, EingangGame::execute(cmd, reg)— die eine Stelle, an der ein Zug endet (Modifikatoren ticken, Event-Warteschlange leerarbeiten). Zwölf Commands, alle kosten einen Zug (eincosts_turn-Flag waere eine Ausnahmeliste); Angriff und Flucht sind keine Varianten, sondernUseAbility("core:attack")undUseAbility("core:flee")— damit moddbar. Ein gemeinsamerVec<Screen>fuer Screens und Popups; Screens gebenResponsezurueck statt selbst zu schieben, sonst haelt ein Screen ein&mutauf dieVec, in der er liegt. EinResponse::Commandschliesst alle Overlays — dasselbe Flag, das schon fuers Rendern existiert. Zehn Screens,ListStatein der Variante (der Stack erhaelt den Cursor gratis, solange der Screen liegt). NurKeyEventKind::Press: beiRepeatbestimmte die Tastaturwiederholrate die Spielzeit; gegen zaehes Laufen hilft ein Renn-Kommando ingame/. Tastenbelegung fest, Pfeile undhjkl. ratatui-Freiheit vongame/prueft ein#[test]statt eines Workspace. Nachtrag an 01:symboltraegt das Zeichen und einen Farbnamen selbst —ui/theme.rsist Code und damit fuer Modder unerreichbar. - 12 — Prototyp: ratatui-Durchstich (#13) — Das Modell aus 11 traegt. Primaerquelle: Branch
prototyp/12-ratatui-durchstich,cargo run --example durchstich. Bestaetigt:mod gamebleibt ratatui-frei ohne Disziplin, Grid als eigenesimpl Widgetueberbuf[(x,y)].set_symbol(…),Vec<Screen>mit Overlay-Flag fuer Rendern und Auto-Schliessen,Responsestatt 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); undCommandmussClonesein, damitConfirmes herausgeben kann. Ungetestet bliebRegistriesnebenGame— der Durchstich laedt keine. - 13 — Script-Engine wählen (#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_operationsausdruecklich 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_scriptals neunte Aktion, Scripts als elfte Registry. Risiken beobachtet mit benanntem Ausstiegssignal;smartstringist 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 (#15) — Ein Zug = Spielerhandlung plus alle Gegnerreaktionen, sonst tickte ein Debuff bei drei Gegnern viermal. Initiative als
i32am Wesen statt als Reihenfolgeliste (die waere eine zweite Indexsammlung mit demselben Ungueltigkeitsproblem); tote Gegner erst am Ende vonexecute, nach dem Leeren der Warteschlange. Trefferformel und Schadenskette mit drei Klemmbereichen —0.8bei Resistenz, weil1.0Immunitaet 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 ⇒Rejectedohne Zug. Achte Registry: Wesen — die Luecke lag seit 02 offen;aials geordnete Bedingungsliste.[balance]verschmilzt feldweise, ausdrueckliche Ausnahme zu 03 (festes Schema). Vier Nachtraege:scaleundfleean 01, drei Felder an 07, zwei Events an 08,GameOveran 11. - 15 — Progression und Level-up (#16) — Ein Aufstieg gibt genau einen frei verteilbaren Punkt auf die fuenf Grundwerte;
AssignPointsist 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_exponentin[balance]). Erfahrung aus Toetungen (exp_rewardam Wesen) und Quests (give_exp, zehnte Aktion), nicht fuers Betreten einer Etage — das belohnte das Durchrennen. Die Zuwachs-Regel bleibt eng: nur Aufstieg undAssignPointsheben den Lebensstand mitmax_health, Ausruestung nie — sonst „heilt ein Ring beim Anlegen" (01). Rasse und Klasse bleiben flach,per_levelentfaellt ersatzlos; was sie traegt, sind Faehigkeiten.leveldarf modifiziert werden, aber Punkte haengen anbase.level— ein+1 level-Ring schenkt keinen. Vier Nachtraege:give_expan 01,current_exp/unspent_pointsanGamestatt ans Wesen (07 hatte die Platzierung hierher delegiert),AssignPointsan 11, zwei Balance-Knoepfe plusexp_rewardan 14. - 16 — Loot-Tabellen (#17) — Zwoelfte Registry
[[loot_tables]], eine Maschinerie fuer Gegenstaende und Affixe. Gewichte statt Prozent, weil Prozente sich auf 100 summieren muessen und ein hinzugefuegter Mod-Eintrag die Summe kaputt macht. Nieten sind gewoehnliche Eintraege (keindrop_chance), Tabelle-in-Tabelle liefert Seltenheitsstufen, ohne dass „Seltenheit" ein Begriff wird. Affix-Stufen statt Affix-Werte — eine Staerke je Exemplar haette Save-Format, Preisformel und Stapelregel aus 07 auf einmal gekostet.luckhat genau eine Regel: Chance auf einen zusaetzlichen Zug auf jeder Tabelle — wirkt dreifach (mehr Gegenstaende, mehr Affixe, haeufiger die staerkere Stufe), ohne dreimal geschrieben zu werden. Der Mod-Einhaengepunkt existiert schon: Tabelle-in-Tabelle plus zwei Ueberschreibungen, kein zweiter Mechanismus. Bodenbeute wird erst beim Aufheben gezogen (Roomhaelt eineloot_tableplus Positionen), moeglich nur weil alle Haufen dasselbe Symbol zeigen — dadurch wirkt ein Gluecksring auch auf das, was schon liegt.guaranteed = truefuer Bosse, Mehrfachziehung erlaubt. Nachtraege:Room-Umbau und optionales Prefab-lootan 10,LUCK_FACTORan 14.
Not yet specified
-
Balancing — konkrete Zahlen für Attribute, Schaden, Preise und Erfahrung über fünf Etagen. Wartet auf Kampfformeln (14), Progression (15) und Loot (16); vorher gibt es nichts zu balancieren.
-
Boss-Design — ein Boss ist derzeit ein Wesen mit
ai-Liste (14) in einem Prefab mitcore:boss-Tag (10). Was fehlt, sind Phasen: „ab 50 % Leben ein anderes Verhalten" wäre mit derai-Bedingungsliste sogar ausdrückbar, „ab 50 % ändern sich die Werte" nicht. Berührt Stufe (c) des Mod-Supports. -
NPC- und Handelsmodell — Warenbestand, Dialogdaten. Wartet auf das Event-System (08). Die Preisformel steht bereits in 07; weil 07 Backtracking ausgeschlossen hat, muss ein Händler auf jeder Etage erreichbar sein.
-
Inventar-UX — Gewicht oder Kapazität (
carry_capacityist 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 = truean der letzten Etage plus eine Skalierungsformel. Berührt drei getroffene Entscheidungen — die Siegbedingung (10),GameOver { outcome: Won }(14) und diefloors-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.
- Party-System — bewusst gegen Solo entschieden; ein Wechsel wäre ein Umbau der Kampfarchitektur, keine Erweiterung.
- Multiplayer
- Grafik jenseits des Terminals