# Dateiformat 1.0 — `mobger.arrows`

Stand: 2026-09-12 · Spielversion des Formats: **1.0**

Eine Spieldatei ist eine UTF-8-kodierte JSON-Datei. Sie beschreibt einen Startzustand,
wahlweise das Zielbild und die Klickfolge dorthin. Das Spiel liest aus der Datei **nur
die hier aufgeführten Schlüssel** (Weißliste); jeder weitere Schlüssel wird übergangen
und lediglich als Hinweis gemeldet. Eine Datei darf also gefahrlos Zusatzangaben wie
`created`, `author`, Punktestände oder einen ganzen Sitzungsmitschnitt mitführen.

---

## 1. Vollständiges Beispiel

```json
{
  "format": "mobger.arrows",
  "version": "1.0",
  "settings": {
    "width": 5,
    "height": 4,
    "fillRatio": 0.6,
    "wallMode": "reflect",
    "clickCount": 1,
    "showPathHint": false
  },
  "start":  { "arrows": [ { "n": 1, "x": 0, "y": 0, "dir": "E" } ] },
  "target": { "arrows": [ { "n": 1, "x": 1, "y": 0, "dir": "E" } ] },
  "solution": [1]
}
```

---

## 2. Feldliste

### 2.1 Oberste Ebene

| Feld | Pflicht | Typ | Wertebereich | Bedeutung |
|---|---|---|---|---|
| `format` | ja | Zeichenkette | genau `"mobger.arrows"` | Kennung. Weicht sie ab, wird die Datei abgelehnt — sie gehört zu einem anderen Spiel. |
| `version` | ja | Zeichenkette | `"<haupt>.<neben>"`, aktuell `"1.0"` | Formatfassung der Datei. Siehe Abschnitt 4. |
| `settings` | ja | Objekt | siehe 2.2 | Brettmaße und Spielschalter. |
| `start` | ja | Objekt | siehe 2.3 | Der Ausgangszustand des linken Bretts. |
| `target` | nein | Objekt | siehe 2.3 | Das gesuchte Bild des rechten Bretts. Fehlt es, wird es aus `start` + `solution` gerechnet; fehlt beides, würfelt das Spiel ein neues Ziel. |
| `solution` | nein | Feld von Zahlen | je `1 … n`, Länge `= settings.clickCount` | Die erzeugende Klickfolge (Pfeilnummern in Klickreihenfolge). Fehlt sie, sucht der Solver sie per Breitensuche nach. |

### 2.2 `settings`

| Feld | Pflicht | Typ | Wertebereich | Vorgabe | Bedeutung |
|---|---|---|---|---|---|
| `width` | ja | ganze Zahl | `2 … 20` | `5` | Spalten des Rasters, `x = 0 … width-1` von links. |
| `height` | ja | ganze Zahl | `2 … 20` | `4` | Zeilen des Rasters, `y = 0 … height-1` von **oben**. |
| `fillRatio` | nein | Bruchzahl | `0.1 … 0.9` | `0.6` | Anteil belegter Felder beim Würfeln eines neuen Spiels: `n = round(fillRatio · width · height)`. Für eine geladene Datei nur informativ — die Pfeilzahl steht in `start.arrows`. |
| `wallMode` | ja | Zeichenkette | `"reflect"` \| `"teleport"` | `"reflect"` | Verhalten an der Wand. `reflect` kippt die senkrechte Richtungskomponente **dauerhaft**, `teleport` rechnet modulo (Torus). |
| `clickCount` | nein | ganze Zahl | `1 … 6` | `1` | Wie viele Klicks vom Start zum Ziel führen. |
| `showPathHint` | nein | Wahrheitswert | `true` \| `false` | `false` | Zeigt in der Statuszeile „Folge bis hier korrekt". Nur bei `clickCount ≥ 2` wirksam. |

Warum `width, height ≥ 2`: bei einer Breite von 1 liegt das Zielfeld nach dem Kippen
wieder außerhalb — der Pfeil könnte sich in der Reflexion nie bewegen (Konzept 2.4).

### 2.3 `start` und `target`

Beide Objekte haben denselben Bau:

| Feld | Pflicht | Typ | Bedeutung |
|---|---|---|---|
| `arrows` | ja | Feld von Pfeilobjekten | Die Pfeile des Zustands. Reihenfolge im Feld ist gleichgültig, das Spiel sortiert nach `n`. |

Ein **Pfeilobjekt**:

| Feld | Pflicht | Typ | Wertebereich | Bedeutung |
|---|---|---|---|---|
| `n` | ja | ganze Zahl | `1 … Anzahl der Pfeile`, lückenlos und eindeutig | Nummer und zugleich Identität des Pfeils. Sie wandert mit ihm und wird als Index am Zeichen angezeigt. |
| `x` | ja | ganze Zahl | `0 … width-1` | Spalte, von links gezählt. |
| `y` | ja | ganze Zahl | `0 … height-1` | Zeile, von **oben** gezählt. |
| `dir` | ja | Zeichenkette | einer der acht Codes aus 2.4 | Blickrichtung des Pfeils. |

Zusätzlich gilt: **kein Feld doppelt belegt**, und `target.arrows` führt dieselben
Nummern wie `start.arrows`.

### 2.4 Die acht Richtungscodes

| Code | Richtung | `dx, dy` | Zeichen | Unicode |
|---|---|---|---|---|
| `N`  | Nord     | `0,-1`  | ↑ | U+2191 |
| `NE` | Nordost  | `1,-1`  | ↗ | U+2197 |
| `E`  | Ost      | `1,0`   | → | U+2192 |
| `SE` | Südost   | `1,1`   | ↘ | U+2198 |
| `S`  | Süd      | `0,1`   | ↓ | U+2193 |
| `SW` | Südwest  | `-1,1`  | ↙ | U+2199 |
| `W`  | West     | `-1,0`  | ← | U+2190 |
| `NW` | Nordwest | `-1,-1` | ↖ | U+2196 |

Die Codes sind **Großbuchstaben**. `"e"` oder `"Ost"` gelten als fehlend.

---

## 3. Was beim Import geschieht

1. Die Datei wird als JSON gelesen. Scheitert das, endet der Import mit der Meldung des
   Parsers und der Angabe der Zeile.
2. `format` wird geprüft. Passt die Kennung nicht, endet der Import.
3. `version` wird gegen die aktuelle Spielversion `1.0` gehalten (Abschnitt 4).
4. Aus jedem Objekt werden **nur die oben gelisteten Schlüssel** übernommen. Alles
   andere landet als „Unbekannt (übergangen)" in den Hinweisen — es ist **kein Fehler**.
5. Fehlt ein Pflichtwert oder liegt er außerhalb seines Wertebereichs, wird der Pfad des
   Felds gemeldet (`settings.wallMode`, `start.arrows[2].dir`). Der Index in eckigen
   Klammern ist die **Position im JSON-Feld und zählt ab 0** — `arrows[2]` ist der dritte
   Pfeil, nicht der Pfeil mit der Nummer 2.
   Die Prüfung bricht **nicht** beim ersten Fehler ab: erst werden alle Lücken gesammelt,
   dann wird der Import abgelehnt. So muss die Datei nur einmal berichtigt werden.
6. Fehlt `solution`, rechnet der Solver sie nach. Findet er nichts, läuft das Spiel
   **ohne** Hinweis und **ohne** Fehlerzählung weiter und sagt das in der Statuszeile an.

---

## 4. Versionsvergleich und Fehlermeldungen

Jede Meldung nennt die **Version der Datei gegen die aktuelle Spielversion**:

```
Import abgebrochen.
  Datei-Version: 0.9   Aktuelle Spielversion: 1.0
  Fehlend: settings.wallMode, start.arrows[2].dir
  Hinweis: Ältere Datei-Fassung 0.9; gelesen wird sie als 1.0.
  Unbekannt (übergangen): _hinweis, settings.gravity
```

Lädt eine Datei dagegen durch, beginnt der Block mit `Import mit Hinweisen.`; ist gar
nichts anzumerken, lautet die ganze Meldung `Import erfolgreich.`

| Fall | Verhalten |
|---|---|
| `version` fehlt | Meldung „Datei nennt keine Version, erwartet 1.0"; es wird so weit wie möglich geladen. |
| Hauptversion `> 1` (z. B. `"2.0"`) | **Harte Absage**: „Datei stammt aus einer neueren Fassung." Es wird nichts geladen. |
| Hauptversion `1`, Nebenversion abweichend | Es wird geladen; jede Lücke wird einzeln benannt. |
| Gleiche Version, Pflichtfeld fehlt | Import abgebrochen, fehlende Pfade werden benannt. |

---

## 5. Beispieldateien

| Datei | Zweck |
|---|---|
| [`../Beispiel/startzustand_3x2_v1.json`](../Beispiel/startzustand_3x2_v1.json) | Handgerechnetes Kleinbeispiel, 3 breit × 2 hoch, Reflexion, ein Klick auf Pfeil 1 führt zum Ziel. Enthält mit `_hinweis` bewusst einen Schlüssel außerhalb der Weißliste. |
| [`../Beispiel/fehlerhaft_version_0_9.json`](../Beispiel/fehlerhaft_version_0_9.json) | Absichtlich fehlerhaft: Version `0.9`, `settings.wallMode` fehlt, dem dritten Pfeil fehlt `dir`, und `settings.gravity` kennt das Format nicht. Erzeugt genau die Meldung aus Abschnitt 4. |

Im Kleinbeispiel steckt der ganze Zugmechanismus: Pfeil 1 und 2 ziehen je ein Feld nach
Osten, Pfeil 3 will nach Südwesten, findet `(1,1)` besetzt, gleitet weiter, stößt unten an
die Wand, kippt dadurch **dauerhaft** auf Nordwest und landet auf dem inzwischen frei
gewordenen Feld `(0,0)`.

---

*Diese Dokumentation wurde mit Claude Code 2.1.267 (Modell Opus 5, `claude-opus-5[1m]`)
am 2026-09-12 erstellt.*
