# Spec: Präsentations-/Ausgabe-Schicht (Teilprojekt 1)

Stand: 2026-05-31. Erstes Teilprojekt aus [enhancement-ideas.md](../../enhancement-ideas.md).
Baut auf der v1.0-Baseline (Tag `v1.0`) auf.

## 1. Kontext & Ziel

`GameIO` bietet aktuell nur `readLine()` + `write(String)`; beide Frontends
(`ConsoleIO`, `SwingIO`) schieben rohen Text durch. Ziel: **reichere, strukturierte
Ausgabe** in beiden Modi, ohne den für beide Frontends **identischen Game-Loop**
aufzugeben (Kernwert der Architektur).

Zielbild (bestätigt im Brainstorming):
- Konsole: **Stufe B** – Boxed Heading, gelabelte Sektionen, persistente HUD-Zeile;
  ASCII-Art nur für große Momente.
- GUI ist der **Fokus** (mehr Freiheit), Konsole wird weiter unterstützt.
- Semantische Farb-Rollen für Items / NPCs / Exits / Headings / Gefahr.
- **Progressive Disclosure** (nicht überladen) und **Info auf Regionen verteilen**
  (Karte ersetzt später die Exit-Zeile).

## 2. Scope

**In Scope:**
- Semantisches Ausgabe-Modell (`Style`, `Span`, `StyledText` + Builder).
- High-Level-Views: `RoomView`, `Hud`.
- Erweiterung der `GameIO`-Schnittstelle (additiv, `write(String)` bleibt als Default).
- `ConsoleIO` als Renderer: ANSI-Farben + Box-Drawing (Stufe B), HUD-Zeile,
  ASCII-Art-Banner-Mechanismus, gestufte Glyph-Fähigkeit.
- `SwingIO` als Renderer: Region-Layout (HUD oben, Output mittig, Seitenpanel
  rechts reserviert, Input unten), `JTextPane`/`StyledDocument`, **gebündelter Font**.
- Migration der bestehenden Commands auf die neue API; `TestIO` angepasst.

**Out of Scope (jeweils eigene spätere Specs):**
- Tatsächliches Zeichnen der **Karte** (Teilprojekt 2) – Seitenpanel ist nur Slot,
  zeigt bis dahin die Exit-Liste.
- **Hauptmenü/Settings** (6), **Speichern/Laden** (7), **Musik** (8).
- Neue **Inhalte/Mechaniken** (Quests, Licht, Item-Kombination).

Die Schnittstellen lassen bewusst Slots (Seitenpanel, HUD-Felder) für diese
späteren Teile.

## 3. Architektur (Approach 2)

**Commands erzeugen Bedeutung, kein Format.** Ein Command baut ein semantisches
Objekt (`StyledText` oder eine View) und übergibt es an `GameIO`. Jedes Frontend
**rendert** es selbst: `ConsoleIO` → ANSI/Box-Drawing, `SwingIO` → Swing-Komponenten
und **eigenes Layout**. Der Game-Loop bleibt unverändert.

```mermaid
flowchart LR
    CMD["Command<br/>(z.B. LookCommand)"] -- "baut" --> VIEW["RoomView / StyledText / Hud<br/>(semantisch, kein Format)"]
    VIEW -- "GameIO.showRoom() / print() / setHud()" --> IO{{"GameIO"}}
    IO --> CON["ConsoleIO<br/>ANSI + Box-Drawing"]
    IO --> GUI["SwingIO<br/>JTextPane + Panels"]
```

## 4. Komponenten & API

Neues Subpackage `io.text` für das Modell **und** die Views (`Style`, `Span`,
`StyledText`, `RoomView`, `Hud`); die Renderer (`ConsoleIO`, `SwingIO`) bleiben in `io`.

### 4.1 Style (enum)

```java
public enum Style { PLAIN, HEADING, ITEM, NPC, EXIT, DANGER, DIM }
```

Kleines, bewusst begrenztes Rollen-Set. Mapping auf konkrete Farben/Attribute
liegt im jeweiligen Renderer, nicht am Style.

### 4.2 Span & StyledText

```java
public record Span(String text, Style style) {}

public final class StyledText {
    private final List<Span> spans;            // immutable
    public List<Span> spans() { ... }
    public String plainText() { ... }          // Spans konkateniert, ohne Format

    public static StyledText of(String s) { ... }   // ein PLAIN-Span
    public static Builder builder() { ... }

    public static final class Builder {
        Builder plain(String s);  Builder heading(String s);
        Builder item(String s);   Builder npc(String s);
        Builder exit(String s);   Builder danger(String s);
        Builder dim(String s);    StyledText build();
    }
}
```

Beispiel:
```java
io.print(StyledText.builder()
    .plain("You pick up ").item("the brass lamp").plain(".").build());
```
Zeilenumbrüche stehen als `\n` im Text der Spans.

### 4.3 Views

```java
public record RoomView(String name, String description,
                       List<String> items, List<String> npcs,
                       List<String> exits) {}

public record Hud(String location, int gold, int turn, boolean lightOn) {}
```

Views tragen bereits **anzeigefertige Strings** (Namen aufgelöst), aber **kein
Layout**. `RoomView.exits` versorgt vorerst das GUI-Seitenpanel; sobald Teilprojekt 2
die Karte zeichnet, blendet der GUI-Renderer die Exit-Sektion aus.

### 4.4 GameIO (erweitert, additiv)

```java
public interface GameIO {
    String readLine();
    void print(StyledText text);     // gestylter Ausgabe-Block
    void showRoom(RoomView room);    // High-Level-Raumansicht
    void setHud(Hud hud);            // HUD-Region aktualisieren

    default void write(String s) {   // Komfort + Rückwärtskompatibilität
        print(StyledText.of(s));
    }
}
```

`write(String)` bleibt als Default → bestehende Aufrufstellen kompilieren weiter;
Migration kann schrittweise erfolgen.

### 4.5 ConsoleIO (Renderer)

- `print`: Spans → ANSI-Sequenzen (Rollen→Farbe), dann `println`.
- `showRoom`: Stufe-B-Layout mit Box-Drawing-Rahmen (Heading + Sektionen
  „You see / Here is / Exits"), Rollen-Farben.
- `setHud`: dim gefärbte Statuszeile (`[ Kitchen · 0 gold · turn 7 · light: off ]`).
- **ASCII-Art-Banner**: Methode/Helfer für große Momente (Start, Finale); Kunst aus
  Ressourcen geladen. Mechanismus in Scope, konkrete Kunstwerke folgen mit Inhalten.
- **Konfiguration** (Defaults sicher, Verdrahtung mit Settings-Menü später):
  - `ColorMode`: an/aus; Auto-Erkennung (`System.console() == null` → keine ANSI).
  - `GlyphMode`: `ASCII | UNICODE | GLYPH`, Default `UNICODE` (Box-Drawing breit
    unterstützt; Nerd-Glyphen nur bei explizitem Opt-in). Bei `ASCII` degradieren
    die Rahmen auf `+`, `-`, `|` statt der Unicode-Box-Drawing-Zeichen.

### 4.6 SwingIO (Renderer) + Layout

Region-Layout (bestätigt, Variante A):
- **NORTH**: HUD-Leiste (`JPanel` mit `JLabel`s) – Ort · Gold · Zug · Licht.
- **CENTER**: `JTextPane` mit `StyledDocument` (Farben/Stile via `AttributeSet` pro
  `Style`), scrollbar.
- **EAST**: Seitenpanel, reserviert für Karte; zeigt bis dahin `RoomView.exits` als Liste.
- **SOUTH**: Eingabefeld (bestehende `LinkedBlockingQueue`-Brücke bleibt).

**Gebündelter Font:** `.ttf` unter `src/main/resources/fonts/`, via
`Font.createFont(Font.TRUETYPE_FONT, stream)` geladen und registriert → Farben und
**Glyphen funktionieren ohne Installation auf dem Spieler-System**. Fallback auf
`Font.MONOSPACED` bei Ladefehler (geloggt).

`print`/`showRoom` schreiben gestylte Spans in das `JTextPane`; `setHud` aktualisiert
die Labels. Alle Swing-Mutationen via `SwingUtilities.invokeLater` (wie bisher).

## 5. Datenfluss

```
Command → baut RoomView/StyledText/Hud → GameIO.{showRoom|print|setHud}
        → Frontend-Renderer → Konsole (ANSI) bzw. GUI (Swing)
```
Game-Loop, Parser, Registry, Domain: **unverändert**.

## 6. Nerd-Font / Glyph-Strategie (zusammengefasst)

- **GUI**: Font mitliefern (bundled `.ttf`) → Problem gelöst, keine Installation nötig.
- **Konsole**: Font nicht kontrollierbar → **gestufte Fallbacks** (`ASCII` →
  `UNICODE`-Box-Drawing → `GLYPH`). Default `UNICODE`. Keine harte Abhängigkeit von
  installierten Nerd Fonts.

## 7. Migration der Commands & Tests

- `LookCommand`: statt `StringBuilder` eine `RoomView` bauen → `io.showRoom(view)`.
- Übrige Commands (`Take`, `Drop`, `Use`, `Read`, `Examine`, `Talk`, `Give`,
  `Inventory`, `Help`): `io.print(StyledText…)` mit Rollen, wo es Mehrwert bringt;
  sonst genügt das Default-`write(String)`. Schrittweise, nicht Big-Bang.
- `TestIO`: implementiert `print`/`showRoom`/`setHud` durch **Flattening auf
  Klartext** (`StyledText.plainText()` bzw. RoomView→Textblock), abgelegt in
  `outputs`. `lastOutput()`/`allOutput()` liefern weiterhin Klartext → die
  bestehenden `.contains(...)`-Assertions der 67 Tests bleiben gültig.

## 8. Fehlerbehandlung

| Fall | Verhalten |
|---|---|
| Gebündelter Font nicht ladbar | Log-Warnung, Fallback `Font.MONOSPACED` |
| Terminal ohne ANSI / Umleitung | Auto-Erkennung → Farben aus, reiner Text |
| `GlyphMode=GLYPH` ohne passende Terminal-Font | Nutzer-Opt-in; kein Auto-Fallback nötig, Default bleibt `UNICODE` |
| `null`-Text an `StyledText.of` | `IllegalArgumentException` (wie bestehende Konventionen) |

## 9. Testing-Strategie

- **Unit**: `StyledText`-Builder & `plainText()`, `Span`, Immutabilität von
  `RoomView`/`Hud`.
- **ConsoleIO**: Rendern einer bekannten `RoomView`/`Hud` mit Farbe AUS gegen einen
  `StringWriter`/`PrintStream` (Test-Konstruktor existiert bereits) → strukturelle
  Assertions (Heading, Labels, Exit-Namen).
- **Command-Tests**: laufen unverändert über das angepasste `TestIO` (Klartext).
- **SwingIO**: nicht unit-getestet (EDT/Swing); Logik dünn halten, manueller
  Smoke-Test. Verifikation per `mvn exec:java@gui`.

## 10. Handoffs an spätere Teilprojekte

- **Karte (2)**: GUI-Seitenpanel + künftige `showMap(MapView)`-Methode;
  `RoomView.exits` ist bis dahin Platzhalter-Quelle.
- **Menü/Settings (6)**: verdrahtet später `ColorMode`/`GlyphMode`/Lautstärke.
- **Save (7)**, **Musik (8)**: nutzen die HUD/Region-Slots, hier nicht implementiert.

## 11. Offene Detailfragen (in Implementierung entscheidbar)

- Konkrete Wahl des gebündelten Fonts (freier Monospace bzw. Nerd-Font-Variante,
  Lizenz prüfen).
- Exakte ANSI-Farbpalette pro Rolle.
- Ob `Hud` schon ein `lightOn` führt, obwohl die Licht-Mechanik erst mit den Inhalten
  kommt → Feld vorsehen, vorerst konstant `false`/ausgeblendet.
