# Spec: Licht & Dunkelheit (Mechanik-Erweiterung) Stand: 2026-05-31. Kleine Mechanik aus dem Spine (#3 der Mechanik-Liste), die das Fundament (Flags/Items) nutzt. Strenge: **Modell B** (Dunkelheit blockiert). ## 1. Kontext & Ziel Dunkle Räume sollen den schaltbaren `lamp` sinnvoll machen: Ohne aktive Lichtquelle kann man einen dunklen Raum **nicht betreten**, nichts sehen (`look`), und nichts aufnehmen/untersuchen. Die HUD-Zeile `light: on/off` wird endlich verdrahtet. ## 2. Scope **In Scope:** - `Room.dark` (Bool, optional), `Item.light` (Bool, optional). - `Light`-Helper: `isLit`, `carryingLight`. - Command-Gating: `go` (Eintritt), `look`, `take`, `examine`; HUD-`lightOn`. - Demo: Dungeon dunkel → Lampe nötig. **Out of Scope:** Lampen-Brennstoff/zeitlich begrenztes Licht; Lichtradius über Räume hinweg; Zufall. ## 3. Daten (rückwärtskompatibel) - **`Room.dark`**: `@Setter`-Feld (default `false`), **kein** Konstruktor-Argument → keine Test-Brüche. Vom `RoomFactory` aus `RoomDto.dark` gesetzt. YAML: `dark: true`. - **`Item.light`**: `boolean` am abstrakten `Item` (via `@SuperBuilder`), default `false`. Vom `ItemFactory` aus `ItemDto.light` gesetzt. YAML: `light: true`. - **Aktive Lichtquelle** = `item.light` **und** (kein Switchable **oder** `SwitchableItem.isOn()`). Die `lamp` leuchtet also nur eingeschaltet. ## 4. Helper `Light` (game) ```java static boolean isLit(GameContext ctx, Room room) { if (!room.isDark()) return true; return hasActiveLight(player-inventory) || hasActiveLight(room-items); } static boolean carryingLight(GameContext ctx); // aktive Lichtquelle im Inventar ``` `hasActiveLight(items)` iteriert; `isActiveLight(item)` = `item.isLight()` && (`!(item instanceof SwitchableItem s)` || `s.isOn()`). Räume gelten als beleuchtet, wenn sie nicht dunkel sind **oder** eine aktive Lichtquelle getragen wird **oder** im Raum liegt. ## 5. Command-Gating (Modell B) | Command | Verhalten bei dunklem, unbeleuchtetem Zielraum/aktuellem Raum | |---|---| | `GoCommand` | **Eintritt blockiert**, wenn `!isLit(ctx, target)`: Hinweis-Text, kein Wechsel. (Lichträume bleiben immer betretbar → **kein Soft-Lock**, Rückzug stets möglich.) | | `LookCommand` | `!isLit(ctx, current)` → „It's pitch black; you can't make anything out." statt `RoomView` (Fall: Lampe ging im Raum aus). | | `TakeCommand` | `!isLit(ctx, current)` → „It's too dark to see that." | | `ExamineCommand` | `!isLit(ctx, current)` → „It's too dark to see that." | | HUD | `lightOn` = `Light.carryingLight(ctx)` (in `Game.publishHud`). | Erlaubt im Dunkeln bleiben: `inventory`, `drop`, **`use`** (z. B. Lampe anzünden), `quests`, `map`, `help`. So kann man eine getragene Lampe im Dunkeln anzünden. ## 6. Fehlerbehandlung / Soft-Lock Eintrittssperre statt Innen-Sperre: Man betritt einen dunklen Raum nur **mit** Licht. Geht das Licht drinnen aus, sind `look`/`take`/`examine` blockiert, aber der Rückweg in einen hellen Raum bleibt (Zielraum hell → `isLit` true). Kein Soft-Lock. ## 7. Demo - `lamp`: `light: true` (bereits schaltbar; `use lamp` → an). - `dungeon`: `dark: true`; der Generator steht dort. Ablauf: `take lamp` → `use lamp` (an) → `go south` (jetzt betretbar) → `use generator`. HUD zeigt `light: on`, sobald die Lampe brennt. Gibt man die Lampe später dem alten Mann, verliert man das Licht (thematisch). ## 8. Testing - `Light.isLit`: nicht dunkel → hell; dunkel ohne Licht → dunkel; dunkel + getragene brennende Lampe → hell; dunkel + Lampe **aus** → dunkel; dunkel + brennende Lampe **im Raum** → hell. `carryingLight`. - Command-Gates: `go` in dunklen Raum ohne Licht blockt, mit brennender Lampe ok; `look`/`take` im dunklen Raum blockt. - End-to-End (Konsole): `go south` ohne Licht blockt; `take lamp`/`use lamp`/`go south` betritt; HUD `light: on`. ## 9. Offene Detailfragen (in Implementierung) - Genauer Hinweis-Text der Eintrittssperre (nennt `use lamp`).