Files
Jander_Semester2/Semesterprojekt/docs/superpowers/specs/2026-06-02-content-expansion-design.md
Jean-Luc Makiola 90311a7671 docs: spec for Haunted Manor content expansion (14 rooms)
Condensed 14-room manor on one spine (restore power -> escape) stacking all five
mechanics, plus an optional spirit thread feeding 3 priority endings (no
hard-fail). Pure YAML content on existing engines. Consolidates to one world
(removes the toy test fixture) + a WorldSolvableTest that walks the critical path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 08:48:52 +02:00

14 KiB
Raw Blame History

Spec: Content-Ausbau — „Haunted Manor" (14 Räume)

Stand: 2026-06-02. Backlog #4 (+ #5b Mechanik-Spine). Erster reiner Content- Ausbau: die Engines stehen alle — diese Erweiterung ist überwiegend YAML (rooms/items/npcs/quests/endings/combinations), plus die nötigen Test-Anpassungen.

1. Kontext & Ziel

Die Welt hat heute 5 Räume und eine Mini-Demo. Ziel: eine kondensierte, designte Spielwelt (~14 Räume, 3 Ebenen) mit einem zentralen Bogen („Strom wiederherstellen → entkommen") der den Spieler raumweise führt und alle fünf Mechaniken stapelt, plus ein optionaler Geister-Strang (den ruhelosen Geist zur Ruhe betten) für die beste Ende-Variante. Kein Hard-Fail (keine Tod/Lose-Enden), puzzle-fokussiert.

Bestätigte Entscheidungen (Brainstorming 2026-06-02):

  • Größe: ~14 Räume, kondensiert/handgebaut (kein Füllmaterial).
  • Zentraler Bogen: Strom wiederherstellen (drain → fuse → generator → power_on) schaltet das Obergeschoss frei; leichter Geister-Strang speist die Enden.
  • Enden: 3, priorisiert, kein Hard-Fail: Into the Night (geflohen) / Manor Reclaimed (Strom + raus) / Spirit at Rest (Strom + Geist gebannt + raus).
  • Mechanik-Abdeckung: Licht/Dunkelheit (Keller), verschlossene Exits (Treppe via Strom, Haustür = Ausgang), Flags, Item-Kombination (mehrfach), Quest-Kette + Enden, go to <raum>, Musik pro Raum.

2. Scope

In Scope (Content, YAML in src/main/resources/world/):

  • rooms.yaml: 14 Räume, Exits (N/S/E/W), dark, music, exitLocks, descriptionStates.
  • items.yaml: ~16 Items (siehe §5), inkl. produzierter (unplatzierter) Items.
  • combinations.yaml: 5 Rezepte (§6).
  • npcs.yaml: der old_man (Hinweis-NPC, flag-bedingte Dialoge, 1 Reaktion).
  • quests.yaml: restore_power (autoStart, 2 Stages) + lay_to_rest (per Effekt gestartet).
  • endings.yaml: 3 priorisierte Enden.
  • game.yaml: Titel/Startraum/Welcome aktualisieren (Start = foyer).
  • tutorial.yaml: ggf. minimal anpassen (Start ist jetzt foyer; der go-Schritt führt nach Norden in die Grand Hall — bestehende verb-basierte Schritte bleiben gültig).

In Scope (Tests / eine Welt):

  • Toy-Test-Fixture entfernen (src/test/resources/world/*), damit die loader-basierten Tests die echte ausgelieferte Welt prüfen (heute beschattet das Fixture die Produktionswelt → Produktion ist ungetestet). src/test/resources/badtut/ bleibt (separate Basis für den Malformed-Test).
  • WorldLoaderTest + TutorialLoaderTest an die echte Welt anpassen (Form-Invarianten statt Toy-Fixture-Werte).
  • Neuer WorldSolvableTest: läuft den kritischen Pfad programmatisch (Bewegung + Combinations.tryUse + Flag-Checks) und prüft, dass power_on, ghost_banished und ein Sieg-Ende erreichbar sind — automatische Lösbarkeits-Garantie.

Out of Scope:

  • Engine-/Code-Änderungen (außer Tests) — alle Mechaniken existieren bereits.
  • Audiodateien (externer music/-Ordner; YAML referenziert nur Namen).
  • Neue Mechaniken (Zahlenschloss/Code-Eingabe etc.) — bewusst vermieden (nur vorhandene item+item-Kombinationen, Flags, Locks, Licht).
  • Hard-Fail/Tod-Enden.

3. Weltkarte (Kompass-Exits, ≤4 pro Raum — keine UP/DOWN; Direction ist N/S/E/W)

Ebenen sind abstrakt über Kompassrichtungen verdrahtet (wie heute Keller=Süd etc.).

Raum Exits dark Items NPC Lock
foyer (START) N→grand_hall front_door, lamp
grand_hall S→foyer, N→dining_room, E→kitchen, W→library
kitchen W→grand_hall, S→cellar_stairs matches, letter
library E→grand_hall shovel, journal old_man
dining_room S→grand_hall, N→conservatory, E→grand_staircase torch, portrait
conservatory S→dining_room valve_wheel
grand_staircase W→dining_room, N→landing N→landing requires power_on
cellar_stairs N→kitchen, E→flooded_cellar, W→boiler_room dark
flooded_cellar W→cellar_stairs dark valve
boiler_room E→cellar_stairs dark generator
landing S→grand_staircase, N→master_bedroom, E→study, W→chapel
master_bedroom S→landing locket
study W→landing photograph
chapel E→landing shrine

Alle Verbindungen bidirektional (Gegen-Exit am Zielraum). Der Power-Lock sitzt nur auf dem Aufwärts-Exit grand_staircase → landing; der Rückweg landing → grand_staircase ist immer offen. Keller-Räume sind dark → Eintritt nur mit getragener Lichtquelle (GoCommand/Light gaten das bereits). Der Strom versorgt das Obergeschoss (Treppe/ Lift), nicht die Kellerverkabelung → der Keller bleibt dunkel (Lichtquelle nötig).

4. Kritischer Pfad (Lösbarkeit)

  1. Erkunden Erdgeschoss; in der kitchen lamp + matches nehmen; use lamp → Lampe an (Lichtquelle). (Alternativweg: torch aus dining + use matches on torchlit_torch.)
  2. conservatory (dining N): valve_wheel nehmen.
  3. In den Keller (kitchen S→cellar_stairs — braucht Licht) → flooded_cellar (E): use valve_wheel on valve → Flag cellar_drained + erhält fuse.
  4. boiler_room (cellar_stairs W): use fuse on generator → Flag power_on. (Quest restore_power abgeschlossen → startet lay_to_rest.)
  5. grand_staircase → landing jetzt offen → Obergeschoss.
  6. master_bedroom: locket. study: photograph. chapel: use locket on shrinelocket_placed; use photograph on shrine (requires locket_placed) → ghost_banished.
  7. Zurück zur foyer: use front_door → Flag left_manor → Ende (Priorität, §8).

5. Items (~16)

id Typ Ort Zweck
lamp switchable, light, initialState off foyer (Start) Primäre Lichtquelle (use lamp → an); zugleich gutartiges Tutorial-Ziel (examine/take/use)
matches plain kitchen für lit_torch
letter readable kitchen Lore; old_man-Reaktion
torch plain dining_room für lit_torch
lit_torch plain, light unplatziert Ergebnis matches+torch (alternative Lichtquelle)
shovel plain library Flavor/Lore (kein kritischer Pfad)
journal readable library Lore + Hinweise (Keller, Schrein)
valve_wheel plain conservatory dräniert den Keller
valve plain (Fixture) flooded_cellar Kombi-Ziel
fuse plain unplatziert Ergebnis valve_wheel+valve
generator plain (Fixture) boiler_room Kombi-Ziel → power_on
front_door switchable, initialState off foyer use front_door → setFlag left_manor
locket plain master_bedroom Relikt 1
photograph plain study Relikt 2
shrine plain (Fixture) chapel Kombi-Ziel der Relikte
portrait readable dining_room Lore

„Fixture" = ein nicht-tragbares Element nur als Kombi-Operand gedacht (technisch ein plain-Item im Raum; der Spieler könnte es theoretisch taken — unkritisch, optional per Flavor-Beschreibung entmutigt). lit_torch/fuse sind in items.yaml definiert, aber keinem Raum zugewiesen (entstehen nur per Kombination; CombinationFactory validiert ihre Existenz, SaveCodec erfasst sie nach dem Erzeugen).

6. Kombinationen (combinations.yaml, 5)

a b requires consume produce effects response (Kurz)
matches torch matches, torch lit_torch „Der Streichholz entflammt die Fackel."
valve_wheel valve fuse setFlag cellar_drained „Du kurbelst das Ventil; das Wasser sinkt und gibt eine Sicherung frei."
fuse generator fuse setFlag power_on „Die Sicherung sitzt; der Generator springt an, das Haus erwacht."
locket shrine locket setFlag locket_placed „Du legst das Medaillon auf den Schrein; die Luft erstarrt."
photograph shrine flag locket_placed photograph setFlag ghost_banished erfolg: „…der Geist findet Frieden." / failText: „Der Schrein wirkt unvollständig — etwas fehlt noch."

(Reihenfolge-unabhängig; Operanden aus Inventar oder aktuellem Raum — Fixtures liegen im Raum, Relikte/Werkzeuge im Inventar.)

7. Flags, Quests, NPC

Flags: cellar_drained, power_on, locket_placed, ghost_banished, left_manor.

restore_power (autoStart):

  • Stage 1 — Objective „Drain the flooded cellar." · completion: flag cellar_drained.
  • Stage 2 — Objective „Get the manor's power running." · completion: flag power_on.
  • onComplete: effects [startQuest lay_to_rest, say "With the power back, the upper floor is reachable — and something stirs up there."].

lay_to_rest (autoStart: false; via startQuest):

  • Stage 1 — Objective „Lay the restless spirit to rest in the chapel." · completion: flag ghost_banished.

NPC old_man (library): Greeting + bedingte Dialoge:

  • ohne cellar_drained: Hinweis auf den gefluteten Keller / das Ventil.
  • mit power_on ohne ghost_banished: Hinweis auf Obergeschoss/Schrein/Relikte.
  • mit ghost_banished: dankbarer Abschiedssatz.
  • Reaktion: onReceive letter → poignante Lore-Antwort, consumes: letter, effects [setFlag heard_tale] (rein optional/Flavor; nicht auf dem kritischen Pfad).

8. Enden (endings.yaml, Reihenfolge = Priorität, erstes Match gewinnt)

  1. spirit_at_rest — when [flag left_manor, flag ghost_banished] · victory true · „Du trittst hinaus; hinter dir liegt das Haus endlich still. (Bester Ausgang.)"
  2. manor_reclaimed — when [flag left_manor, flag power_on] · victory true · „Licht in allen Fenstern, der Bann des Hauses gebrochen — du gehst erhobenen Hauptes."
  3. fled — when [flag left_manor] · victory false · „Du fliehst in die Nacht. Sicher — aber das Haus behält seine Geheimnisse."

(ghost_banishedpower_on ⇒ Obergeschoss erreicht, daher genügt in #1 die Kombination left_manor + ghost_banished. Reihenfolge stellt sicher, dass das spezifischste Ende zuerst greift.)

9. Beschreibungs-Varianten & Musik (Politur)

  • flooded_cellar descriptionState: vor cellar_drained „knietief im schwarzen Wasser…", danach „das abgelassene Becken gibt den schlammigen Boden frei…".
  • chapel descriptionState: vor ghost_banished „eine kalte Gegenwart drückt…", danach „nur noch stilles Mondlicht…".
  • Musik (optionale music:-Felder; Dateien extern, fehlend → still): Erdgeschoss manor-theme.ogg; Keller cellar-drone.ogg; Obergeschoss musicbox.ogg; chapel lament.ogg.

10. Tests / „eine Welt"-Konsolidierung

  • Entfernen: src/test/resources/world/{game,rooms,items,npcs,quests,endings,combinations,tutorial}.yaml (Toy-Fixture „Test Manor"). Danach lösen alle new WorldLoader().load()-Aufrufe in Tests die echte Welt (target/classes/world). src/test/resources/badtut/tutorial.yaml bleibt.
  • WorldLoaderTest anpassen: Titel „Haunted Manor", Startraum foyer, Raum-Anzahl (14), Vorhandensein zentraler Items/Exits — Form-Invarianten der echten Welt statt Toy-Werte.
  • TutorialLoaderTest anpassen: lädt jetzt das echte tutorial.yaml (Intro vorhanden, ≥1 Schritt, erster expect=look) statt der 2-Schritt-Fixture.
  • CombinationLoadingTest: bleibt gültig (Produktion enthält matches+torch→lit_torch).
  • SaveServiceTest/MenuTest: laden irgendeine Welt → unverändert grün (State-Roundtrip).
  • Neu WorldSolvableTest (lädt die echte Welt; treibt den kritischen Pfad rein über Domäne + Combinations.tryUse, ohne IO/Loop):
    • Lichtquelle besorgen, in den Keller, valve_wheel+valvecellar_drained + fuse, fuse+generatorpower_on; Treppe-Lock öffnet; Relikte holen, locket+shrinelocket_placed, photograph+shrineghost_banished; use front_door; EndingEngine.triggered liefert spirit_at_rest.
    • Zweiter Pfad: nur power_on + left_manormanor_reclaimed; nur left_manorfled (prüft die Priorisierung + Lösbarkeit der Hauptkette).

11. Architektur-Werte / Risiken

  • Reiner Content auf vorhandenen Engines; datengetrieben (YAML), keine neue Mechanik.
  • Kondensiert/designt: jeder Raum hat Zweck (Item, Lock, Lore oder Knotenpunkt); ≤4 Exits/Raum (Kompass-Constraint respektiert; keine UP/DOWN).
  • Lösbarkeit automatisiert über WorldSolvableTest — ein gebrochener Lock/Flag, der die Welt unlösbar macht, lässt den Build scheitern statt erst im Spiel aufzufallen.
  • Eine Welt (Toy-Fixture entfernt) → die ausgelieferte Welt wird tatsächlich getestet; Risiko: WorldLoaderTest/TutorialLoaderTest sind nun an den Content gekoppelt (gewollt — sie prüfen die echte Welt). Form-Invarianten statt fragiler Exakt-Werte halten die Kopplung beherrschbar.
  • Fixtures als take-bar: valve/generator/shrine sind technisch tragbar; per Beschreibung als ortsfest dargestellt. Wenn der Spieler eines taket, bricht nichts (Kombination findet das Operand auch im Inventar). Bewusst akzeptiert.
  • Tutorial: Start ist foyer mit der lamp als gutartigem Ziel — die Schritte examine/take/use greifen auf die Lampe (der use-Schritt = „Lampe anzünden", exakt der erste sinnvolle Zug), go führt nach Norden in die Grand Hall, go to zurück zur foyer (zwei besuchte Räume). So landet ein neuer Spieler nicht versehentlich auf use front_door (das front_door liegt zwar auch in der foyer, aber die Anweisungen lenken auf die Lampe; und Enden sind während des Tutorials ohnehin unterdrückt). Die bestehenden tutorial.yaml-Texte bleiben gültig; Start-Welcome/Banner in game.yaml an „Haunted Manor / foyer" anpassen.