# 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`).
