# 10 — Raum-Prefab-Format Type: grilling Status: resolved Blocked by: 03 ## Question Wie sieht ein Raum-Prefab in TOML aus, und wie fügt der Generator daraus eine Etage zusammen? Entschieden ist der Hybrid: Modder bauen Prefabs, der Generator setzt prozedural zusammen. Zu klären: - Prefab-Inhalt: Grid als ASCII-Block oder als Koordinatenliste? Wie werden Ausgänge markiert? Wie Spawn-Punkte für Gegner, Loot, NPCs, Treppen? - Wie flexibel ist die Größe — feste Raster oder beliebige Rechtecke, Rotation und Spiegelung erlaubt? - Verbindungsregeln: Wie weiß der Generator, welche Prefabs aneinanderpassen? Tags an den Ausgängen? - Steuerung durch Daten: Welche Prefabs auf welcher Etage, mit welcher Gewichtung? Pflichträume (Eingang, Treppe, Boss) versus optionale? - Garantien: Wie wird sichergestellt, dass die Treppe erreichbar ist? - Wie greift ein Mod ein — nur neue Prefabs hinzufügen, oder auch Generator-Parameter ändern? Ergebnis: Prefab-Schema mit Beispiel plus die Regeln, nach denen der Generator zusammensetzt. ## Vorgabe aus Ticket 07 `Game` besitzt genau **einen** `Floor`, dessen Innenbau — Grid, Räume, Bodenloot, Ausgänge — dieses Ticket entscheidet. Besuchte Etagen werden verworfen, es gibt kein Backtracking: Der Generator muss also keine begehbaren Rückwege garantieren, aber **ein Händlerraum muss auf jeder Etage auftauchen können**, sonst ist Handel nach dem Abstieg unerreichbar. Bodenloot ist `ItemInstance`, also `{ def, affixes }`, nicht bloß eine ID. ## Vorgabe aus Ticket 03 Prefabs sind eine **weitere Registry über dieselbe Ladepipeline**, kein eigener Lader — das beantwortet die letzte Frage oben halb: Hinzufügen ist der Normalfall, Ersetzen geht über `overrides`. Offen bleibt, ob **Generator-Parameter** (Etagen-Tabellen, Gewichtungen, Pflichträume) ebenfalls Registry-Einträge sind — dann mit derselben Maschine moddbar — oder ein Sonderfall. ## Answer Begriffe im [Glossar](../../../CONTEXT.md). ### Etagenaufbau: freie Platzierung mit Korridoren Prefabs werden nacheinander an zufälliger Position gesetzt, Überlappung (plus ein Feld Rand) verworfen, nach N Fehlversuchen aufgegeben — der klassische Rogue-Ansatz. Verbunden wird **Raum `i` mit Raum `i−1`** in Platzierungsreihenfolge, danach ein paar zufällige Zusatzkorridore für Schleifen. Korridore als L-Form, erst horizontal, dann vertikal. Die Kette statt eines minimalen Spannbaums, weil sie **Erreichbarkeit strukturell garantiert**: Hängt jeder Raum am vorigen, ist jeder Raum von jedem erreichbar — ohne Flutfüllung und ohne Reparaturschleife. Das beantwortet die Garantiefrage aus dem Ticketkörper nebenbei mit. Prim wären zwanzig Zeilen für dasselbe Ergebnis mit hübscheren Korridorlängen. **Rotation und Spiegelung sind erlaubt** — acht Varianten je Prefab, und das Drehen eines ASCII-Blocks ist eine Transposition. Achtmal so viel Inhalt aus derselben Handarbeit. Kein Opt-out-Feld: 1.0 hat kein Prefab, dessen Ausrichtung etwas bedeutet. Taucht eines auf, ist `rotate = false` eine Zeile — vorher wäre es ein Schalter, den niemand stellt. ### Ein Prefab ```toml [[prefabs]] id = "core:kreuzung" tags = ["core:normal"] layout = """ ####+#### #.......# #..e...e# +.......+ #..l....# #.......# ####+#### """ ``` | Zeichen | Bedeutung | | Zeichen | Bedeutung | |---|---|---|---|---| | `#` | Wand | | `l` | Beute-Platz | | `.` | Boden | | `>` | Treppe | | `+` | Tür | | `<` | Eingang | | `e` | Gegner-Platz | | `n` | NPC-Platz | Kein eigener Händler-Marker: **Ein Händler ist ein NPC.** Ein zweites Zeichen für denselben Platz wäre dieselbe Sorte Vermischung, die Ticket 02 beim `ItemType` abgeräumt hat. Marker stehen **im Block**, nicht als Koordinatenliste daneben. Der ganze Sinn von ASCII ist, dass ein Modder den Raum *sieht*; Koordinaten daneben zu pflegen wäre die schlechtere Hälfte von beidem. **Türen sind explizit.** Ohne sie stanzt der Generator Löcher in eine von Hand gezeichnete Wand — wer Prefabs baut, will bestimmen, wo man hineinkommt, sonst hätte man auch würfeln können. Korridore dürfen nur an `+` andocken. **Ein `e` sagt „hier steht ein Gegner", nicht *welcher*.** Was einzieht, entscheidet die Etage. Sonst wäre jedes Prefab an eine Schwierigkeitsstufe gebunden und derselbe hübsche Raum ließe sich nicht auf Etage 1 *und* auf Etage 5 verwenden — genau die Wiederverwendung, für die man Prefabs baut. Bei `n` ist es **umgekehrt**: Den NPC nennt das Prefab selbst (`npcs = ["core:blacksmith"]`), nicht die Etage. NPCs skalieren nicht mit der Tiefe — ein Schmied auf Etage 5 ist derselbe Schmied wie auf Etage 1, und ein `core:merchant`-Raum, dessen Händler ausgewürfelt würde, wäre kein Händlerraum. Das Feld selbst gehört dem NPC-Ticket; hier steht nur, auf welcher Seite die Entscheidung liegt. ### Die Etage ```toml [[floors]] id = "core:floor_3" number = 3 size = [80, 40] # Felder rooms = 9 # Zielzahl inklusive Pflichtraeume required = ["core:entrance", "core:stairs", "core:merchant"] # Tags prefabs = [ { tag = "core:normal", weight = 10 }, { tag = "core:treasure", weight = 2 } ] creatures = [ { id = "core:skeleton", weight = 5 }, { id = "core:ghoul", weight = 2 } ] loot = "core:floor_3_table" ``` Ablauf: **Pflichträume zuerst** auf die leere Fläche, danach gewichtet auffüllen bis `rooms` erreicht oder die Versuche erschöpft sind. Ausgewählt wird durchgehend über **Tags**, auch bei den Pflichträumen. IDs würden bedeuten, dass ein Mod keinen eigenen Eingangsraum beisteuern kann, ohne die ganze Etagen-Tabelle zu überschreiben. Dass Pflichträume zuerst kommen, *ist* die Garantie — auf einer leeren Fläche passen sie praktisch immer. Optionale Räume, die nicht mehr passen, entfallen einfach; das ist der Normalfall, kein Fehler. Passt die Flächensumme der Pflicht-Tags **grundsätzlich** nicht in `size`, ist das ein Datenfehler und beim Laden erkennbar: Warnzeile über die Maschinerie aus 04. Zur Laufzeit dann kein Wiederholungszähler und kein Rückfallpfad — die Prüfung sitzt dort, wo der Fehler gemacht wurde. `core:merchant` steht unter `required` und nicht unter `prefabs`: Ticket 07 hat Backtracking ausgeschlossen, der Händler muss also garantiert auftauchen statt gewichtet. Damit beantwortet sich auch die Frage aus der 03-Vorgabe: **Generator-Parameter sind Daten.** `prefabs` und `floors` sind zwei gewöhnliche Registries — ein Mod ergänzt Etage 6 oder ersetzt Etage 3 über `overrides`, ohne dass hier etwas erfunden wird. Die Gesamtzahl steigt damit auf **zehn**. ### Kampf beginnt beim Betreten eines Raums Die Bestellung aus 14, wer `Game.enemies` füllt: **Gegner gehören zum Raum, nicht zur Etage.** Ein `room_entered` in einen Raum mit lebenden Gegnern verschiebt sie nach `Game.enemies` und startet den Kampf; nach dem Sieg gilt der Raum als geräumt und ein erneutes Betreten startet nichts mehr. Der Grund ist, was dadurch **nicht** gebaut werden muss: Frei umherlaufende Gegner bräuchten Pfadsuche, Sichtlinien, Verfolgung und ein Bewegungsbudget pro Zug — ein eigenes System, das niemand bestellt hat und das die Charter mit „Kampf im Menü" auch nicht nahelegt. Die `e`-Plätze aus dem Prefab sind schlicht die Gegnerliste des Raums. ### `Floor` — der Innenbau aus Ticket 07 ```rust struct Floor { number: u8, tiles: Vec, width: u16, height: u16, rooms: Vec, player_room: Option, } struct Room { prefab: PrefabId, // fuer room_entered und Trigger rect: Rect, enemies: Vec, loot: Vec<(Pos, ItemInstance)>, cleared: bool, } enum Tile { Wall, Floor, Door, Stairs, Entrance } ``` Korridore gehören zu **keinem** Raum — sie sind schlicht `Tile::Floor` außerhalb aller `rect`. Dadurch feuert `room_entered` sauber beim Übergang Korridor → Raum, ohne Sonderfall, und `player_room: None` ist ein echter Zustand statt eines Fehlers. Gegner werden bei Kampfbeginn **verschoben, nicht kopiert** — sonst gäbe es zwei Wahrheiten über denselben Gegner. Ladeprüfung über die 04-Maschinerie: **`layout`-Zeilen müssen gleich lang sein**, sonst ist der Block kein Rechteck und die Platzierung rechnet mit Müll. Ein Zweizeiler, der eine ganze Klasse von Modder-Frust abfängt. ### Siegbedingung — bisher nirgends definiert Kein Ticket hatte je festgelegt, was beim Abstieg von der letzten Etage passiert. **`Descend` auf der letzten definierten Etage gewinnt den Lauf.** Es gibt keine Zahl „5" im Code: Die Engine sucht `[[floors]]` mit `number = aktuell + 1` und findet sie keine, ist Schluss. Ein Mod, der Etage 6 ergänzt, verlängert das Spiel ohne eine Zeile Engine-Code — dieselbe Bewegung wie bei `core:attack` in Ticket 11: Was wie Engine-Logik aussieht, ist Inhalt. ### Zwei Nachträge an geschlossene Tickets | Ticket | Nachtrag | |---|---| | **08** | `room_entered { prefab_id }` als **zwölftes** Event. `floor_entered` deckt nur die Etage ab; „die Tür fällt hinter dir zu", ein Hinterhalt oder eine Quest „finde die Bibliothek" brauchen den einzelnen Raum. Neue Maschinerie kostet es nicht — Prefabs sind gewöhnlicher Registry-Inhalt und dürfen Trigger tragen wie alles andere | | **14** | `GameOver { outcome: Died \| Won }` statt nur `GameOver`. Der Bildschirm zeigt in beiden Fällen dasselbe — Etage, Zug, Gold, Verlauf —, nur die Überschrift unterscheidet sich. Zwei Screens für zwei Texte wären Verdopplung | ### Auflagen an andere Tickets - **09 Save-Schema**: Layout oder Seed? Ticket 14 hat einen seedbaren Generator geliefert, Regenerieren *wäre* also möglich — aber der Raumzustand (`cleared`, gefallene Gegner, genommene Beute) lässt sich nicht regenerieren und muss ohnehin mit. 09 entscheidet, ob sich der Seed dann noch lohnt. - **16 Loot-Tabellen**: `loot` an der Etage verweist auf eine Tabelle, die `l`-Plätze ziehen daraus. - **Boss-Design**: Ein Boss ist derzeit ein Gegner in einem Prefab mit `core:boss`-Tag. Phasen fehlen weiterhin.