# Parametrische Wände

Status: Implementiert (Phase A — Typ-System und Resolver in `src/model/`, kein UI).
Dieses Dokument spezifiziert die **Parametrischen Wände**: regelbasierte Definitionen,
die beim Auflösen eine Liste von `Wall`-Elementen erzeugen, anstatt sie einzeln vom
Nutzer zeichnen zu lassen.

Bezugsdokumente: [elements.md](elements.md) (Wand-/Türmodell),
[drawing-tools.md](drawing-tools.md) (Werkzeugsystem, Direktzeichnen),
[state-architecture.md](state-architecture.md) (Projekt-Store),
[resources-graphics.md](resources-graphics.md) (WallType/Component-Auflösung).

Implementierungsdateien:
- `src/model/types.ts` — `ParametricWall`, `ParametricRule` und alle Regel-Varianten.
- `src/model/parametricWalls.ts` — `resolveParametricWall()`, `applyRule()` und
  Hilfsfunktionen.

---

## 0. Überblick

Eine **parametrische Wand** (`ParametricWall`) ist kein festes `Wall`-Element, sondern
ein **Regelwerk**, das beim Auflösen (`resolveParametricWall`) eine Menge von `Wall[]`-
Elementen generiert. Die erzeugten Wände sind gewöhnliche `Wall`-Objekte; sie
unterscheiden sich lediglich in ihrer Herkunft. Das semantische Modell (`Project`)
bleibt die einzige Wahrheit — parametrische Wände sind eine Ressource in der
Ressourcen-Bibliothek, nicht eine separate Laufzeit-Geometrie-Schicht.

```
Project.parametricWalls: ParametricWall[]
         │
         │  resolveParametricWall(pw, floorId, context, defaultWallType)
         ▼
      Wall[]   ──→  normales Rendering über generatePlan / Viewport3D
```

Erzeugte Wände können entweder **temporär** (zur Laufzeit, als Ergänzung zu
`project.walls` im Rendering-Pfad) oder **eingebacken** (als `Wall[]` fest in
`Project.walls` gespeichert) behandelt werden. Phase A legt nur den Auflöser fest;
die Auswahl liegt bei der aufrufenden Komponente.

---

## 1. Motivation

### 1.1 Schnellere Modellierung von Regelgrundrissen

Schweizer Wohnbauten folgen häufig einem 3-m-Achsraster (SIA-Norm, Modul-/
Skelettbauweise). Zwanzig Wände eines Rasters von Hand zu zeichnen ist fehleranfällig
und verhindert spätere parametrische Änderungen (z. B. Geschossanzahl, Rasterweite,
Wandtyp).

Eine `GridRule` erzeugt dieses Muster aus wenigen Parametern (Achsabstand, Richtung,
Bereich) und lässt sich mit einer einzigen Zahl auf „4-m-Büroraster" umstellen.

### 1.2 Kongruenz mit FreeCAD BIM / IFC

FreeCAD BIM kennt **ParametricObjects**, die ihre Geometrie aus Regeln ableiten (z. B.
`ArchWall` mit `Length`, `Width`, `Height`). Obwohl das Datenformat hier kein IFC ist,
schafft ein ähnliches Abstraktionsniveau eine spätere Brücke: Beim IFC-Export können
parametrische Wände als `IfcWallStandardCase` mit konstanten Attributen exportiert
werden — kein Informationsverlust gegenüber manuell gezeichneten Wänden.

### 1.3 Bedingte Wandtypen ohne manuelle Klassifizierung

Außenwände sind dicker als Innenwände; Trennwände zwischen Einheiten erfordern
Schallschutz. Eine `ConditionalThicknessRule` (`condition: "exterior" → thickType`)
weist den richtigen Wandtyp automatisch aus der geometrischen Lage zu — ohne dass der
Nutzer jeden Wandabschnitt einzeln klassifizieren muss.

---

## 2. Architektur

### 2.1 Typen (`src/model/types.ts`)

```ts
/**
 * Eine parametrische Wand-Regel — generiert automatisch Wall[]-Einträge für
 * ein gegebenes Geschoss. Lebt in Project.parametricWalls[].
 */
export interface ParametricWall {
  id: string;
  name: string;
  description?: string;
  /**
   * Geordnete Liste der anzuwendenden Regeln. Spätere Regeln können die
   * Ausgabe früherer verfeinern (z. B. Dickenzuweisung nach Raster).
   */
  rules: ParametricRule[];
  /**
   * Rückfall-Wandtyp, falls eine Regel keinen eigenen `wallTypeId` nennt.
   */
  defaultWallTypeId: string;
}

/** Diskriminierte Union aller Regel-Varianten. */
export type ParametricRule =
  | GridRule
  | ModuleRule
  | ConditionalThicknessRule
  | ReferenceLineRule
  | SequenceRule;
```

### 2.2 Einbettung ins Projekt

```ts
export interface Project {
  // … bestehende Felder …
  /**
   * Parametrische Wanddefinitionen (Ressourcen-Bibliothek). Optional, damit
   * bestehende Projekte/Tests ohne `parametricWalls` gültig bleiben.
   */
  parametricWalls?: ParametricWall[];
}
```

### 2.3 Resolver-Kontext (`src/model/parametricWalls.ts`)

```ts
export interface ParametricContext {
  /** Das Ziel-Geschoss. */
  floor: DrawingLevel;
  /**
   * Optionale Rasterachsen (Phase C: verlinkter Grid-Ressource). Fehlen sie,
   * berechnet die Engine die Achsen aus GridRule.spacing.
   */
  gridAxes?: { x: number[]; y: number[] };
  /**
   * Optionales Clipping-Polygon (Meter). Fehlt es, reicht das Raster über
   * einen Standardbereich (0 … spacing × 10).
   */
  boundaryGeometry?: { boundary: Vec2[] };
  /**
   * Bereits im Projekt vorhandene Wände des Geschosses. Werden von
   * refinierenden Regeln (ConditionalThicknessRule, ReferenceLineRule) genutzt.
   */
  existingWalls?: Wall[];
}
```

---

## 3. Regel-Varianten

### 3.1 GridRule — Achsraster

Erzeugt parallele Wände auf einem gleichmäßigen Raster. Typischer Einsatz: Schweizer
Wohnbau-Achsraster (3 m), Büro-Konstruktionsraster (6 m), strukturelle Raster mit
fester Stützweite.

```ts
export interface GridRule {
  type: "grid";
  /**
   * Optionaler Verweis auf eine Grid-Ressource (Phase C). Für Phase A wird
   * stattdessen `spacing` genutzt.
   */
  gridId?: string;
  /** Rasterabstand in Metern (Default: 3.0). */
  spacing?: number;
  /**
   * Achsrichtungen: „x" = nur Wände entlang der Y-Achse,
   * „y" = nur Wände entlang der X-Achse, „both" = Vollraster.
   */
  directions: "x" | "y" | "both";
  /** Optionaler Verweis auf Clipping-Polygon. */
  boundaryId?: string;
  /** Optionale Wandtyp-Übersteuerung; sonst defaultWallTypeId. */
  wallTypeId?: string;
  /** Lage der Wandachse über die Dicke (Vectorworks-Stil). */
  referenceLine?: WallReferenceLine;
  /** Optionale Höhenübersteuerung in Metern; sonst Geschosshöhe. */
  height?: number;
}
```

**Geometrieausgabe (top-down Grundriss):**

```
directions: "x", spacing: 3.0, Bereich 0…12 m:

y
│
12 ──────────────────────────
│
9  ──────────────────────────
│
6  ──────────────────────────
│
3  ──────────────────────────
│
0  ──────────────────────────
│
└──────────────────────────► x
   0                        12
```

**Wann verwenden:**
- Tragende Wände auf fester Stützweite (Wohnbau 3 m, Büro 6 m).
- Vollraster (`"both"`) für strukturelle Rastersysteme.
- In Kombination mit `ConditionalThicknessRule` zur automatischen Außen/Innen-Klassifizierung.

**Beispiel: Schweizer 3-m-Wohnraster**

```ts
const pw: ParametricWall = {
  id:                "pw-eg-raster",
  name:              "EG Längswände 3m-Raster",
  defaultWallTypeId: "wt-innen-15",
  rules: [
    {
      type:       "grid",
      spacing:    3.0,
      directions: "x",          // Wände in X-Richtung (y = 0, 3, 6, 9, 12)
      wallTypeId: "wt-innen-15",
    },
  ],
};

// Auflösung:
const walls = resolveParametricWall(pw, "floor-eg", {
  floor:            egFloor,
  boundaryGeometry: { boundary: rectBoundary(0, 0, 12, 12) },
}, defaultWallType);
// → 5 Wände bei y = 0, 3, 6, 9, 12, je 12 m lang
```

---

### 3.2 ModuleRule — Bay-/Jochbauweise

Unterteilt eine Referenzspanne in gleiche Module und erzeugt Querwände an jedem
Teilungspunkt. Typisch für Bürogebäude (6-m-Joch) oder Reihenhäuser mit modularer
Erschließung.

```ts
export interface ModuleRule {
  type: "module";
  /** Modulmaß in Metern (z. B. 6.0, 3.6). */
  moduleSize: number;
  /** Ausrichtung der Trennwände: „x" = Querwände senkrecht zu X, „y" = zu Y. */
  direction: "x" | "y";
  /**
   * Optionaler Verweis auf eine Referenzwand, die die Spannweite definiert.
   * Fehlt er, wird die Geschoss-Ausdehnung (Bounding-Box) genutzt.
   */
  referenceWallId?: string;
  /** Optionale Wandtyp-Übersteuerung; sonst defaultWallTypeId. */
  wallTypeId?: string;
  referenceLine?: WallReferenceLine;
  height?: number;
}
```

**Wann verwenden:**
- Wenn sich Querwände aus einer Referenzspanne (Fassade, Achswand) ergeben.
- Vorzug vor `GridRule`, wenn nur in eine Richtung unterteilt wird und eine
  Referenzwand die Spanne definiert.

**Beispiel: 6-m-Bay-Bürogebäude**

```ts
const pw: ParametricWall = {
  id:                "pw-buero-joch",
  name:              "Büro 6m-Joch",
  defaultWallTypeId: "wt-beton-20",
  rules: [
    {
      type:       "module",
      moduleSize: 6.0,
      direction:  "x",    // Querwände senkrecht zur X-Achse
      // referenceWallId: "W-sudfassade" → Spanne aus der Südwand ableiten
    },
  ],
};
// resolveParametricWall → Querwände bei x = 6, 12, 18, 24, 30 (bei 36-m-Fassade)
```

---

### 3.3 ConditionalThicknessRule — Bedingte Wandtypen

Weist bereits erzeugten Wänden (aus vorherigen Regeln in der Sequenz) einen anderen
Wandtyp zu — abhängig von einer Bedingung. Gibt modifizierte **Kopien** zurück; die
Eingabe-Wände werden nicht mutiert.

```ts
export interface ConditionalThicknessRule {
  type: "conditional-thickness";
  /**
   * Bedingung für den Treffer:
   *   • „exterior"  — Wand liegt am Außenrand (Bounding-Box des Kontexts).
   *   • „interior"  — Wand liegt im Inneren.
   *   • „bearing"   — tragende Wand (Heuristikum: Wand läuft ±10° zu X/Y-Achse).
   *   • beliebiger String — benutzerdefiniertes Tag (Phase C: Wall.tags[]).
   */
  condition: "exterior" | "interior" | "bearing" | string;
  /** Ziel-Wandtyp, der bei Treffer gesetzt wird. */
  wallTypeId: string;
}
```

**Wann verwenden:**
- Immer in Kombination mit `GridRule` oder `ModuleRule` (als zweite Regel in
  `ParametricWall.rules`): Raster erzeugt, Dicke verfeinert.
- Wenn Außen- und Innenwände denselben geometrischen Ursprung haben, aber
  verschiedene Aufbauten benötigen.

**Beispiel: Außen dick, Innen dünn**

```ts
const pw: ParametricWall = {
  id:                "pw-eg-komplett",
  name:              "EG Vollraster mit Außenwand-Differenzierung",
  defaultWallTypeId: "wt-innen-15",
  rules: [
    {
      type: "grid", spacing: 3.0, directions: "both",
      wallTypeId: "wt-innen-15",
    },
    {
      type:       "conditional-thickness",
      condition:  "exterior",
      wallTypeId: "wt-aussen-36",   // Außenwände erhalten dicken Aufbau
    },
  ],
};
```

---

### 3.4 ReferenceLineRule — Wandachsen-Lage

Setzt `referenceLine` bei passenden Wänden einheitlich (Vectorworks-Stil: Achse
links/rechts/mittig). Gibt modifizierte Kopien zurück.

```ts
export interface ReferenceLineRule {
  type: "reference-line";
  /** Neue Lage der Wandachse, die einheitlich gesetzt wird. */
  referenceLine: WallReferenceLine;  // "left" | "center" | "right"
  /**
   * Filterziel:
   *   • „all"      — alle Wände im aktuellen Satz.
   *   • „exterior" — nur Außenwände.
   *   • beliebiger String — benutzerdefiniertes Tag (Phase C).
   */
  target: "all" | "exterior" | string;
}
```

**Wann verwenden:**
- Außenwände auf `"left"` setzen (Achse liegt auf der Fassadenfläche).
- Als abschließende Regel in einer `SequenceRule` nach Raster und Dickenzuweisung.

---

### 3.5 SequenceRule — Zusammenfassung von Unterregeln

Fasst mehrere Regeln als atomare Einheit zusammen. Jede Unterregel erhält die Ausgabe
der vorherigen als `existingWalls` — so können spätere Regeln frühere verfeinern.

```ts
export interface SequenceRule {
  type: "sequence";
  rules: ParametricRule[];
  /**
   * Wenn true: Abbruch nach der ersten Unterregel, die mindestens eine Wand
   * generiert/verändert hat (Short-Circuit-Fallback).
   */
  stopOnMatch?: boolean;
}
```

**Wann verwenden:**
- Um eine zusammengehörige Kombination (Raster → Dicke → Referenzlinie) als
  Untermodul wiederzuverwenden — z. B. in unterschiedlichen Geschossen mit leicht
  abweichenden Parametern.

---

## 4. Resolver-API (`src/model/parametricWalls.ts`)

```ts
/**
 * Löst ein ParametricWall-Regelwerk zu einem Wall[]-Array für ein gegebenes
 * Geschoss auf.
 *
 * Ablauf:
 *   1. Regelwerk sequenziell ausführen; jede Regel erhält die Ausgabe der
 *      vorherigen als existingWalls (ermöglicht Verfeinerung).
 *   2. Duplikate (gleicher Start-/Endpunkt innerhalb tolerance) entfernen.
 *   3. Bereinigte Wall[]-Liste zurückgeben.
 *
 * Die Ausgabe ist sofort bereit zur Einfügung in project.walls. Es werden
 * keine Seiteneffekte erzeugt — kein Store, kein Dispatch, kein React.
 *
 * @param parametricWall  Das Regelwerk.
 * @param floorId         ID des Ziel-Geschosses.
 * @param context         Kontext (Geschoss-Objekt, Grid-Achsen, Grenzen, …).
 * @param defaultWallType Fallback-Wandtyp, wenn eine Regel keinen nennt.
 * @param tolerance       Näherungstoleranz für Duplikat-Erkennung (Meter, Default 0.01).
 * @returns Wall[]-Array, bereit zur Einfügung.
 */
export function resolveParametricWall(
  parametricWall: ParametricWall,
  floorId: string,
  context: ParametricContext,
  defaultWallType: WallType,
  tolerance?: number,
): Wall[];

/**
 * Dispatcher: delegiert eine Regel an die passende Implementierung.
 * Exportiert für Unit-Tests und erweiterbare Regeltypen.
 */
export function applyRule(rule: ParametricRule, ctx: RuleCtx): Wall[];

/**
 * Entfernt doppelte Wände: zwei Wände gelten als Duplikat, wenn Start- und
 * Endpunkt jeweils innerhalb tolerance übereinstimmen (vorwärts und rückwärts).
 */
export function deduplicateWalls(walls: Wall[], tolerance?: number): Wall[];
```

### 4.1 Höhenauflösung

Die Wandhöhe (`Wall.height`) ergibt sich nach folgender Priorität:

1. `rule.height`, falls an der einzelnen Regel gesetzt.
2. `context.floor.floorHeight` des Zielgeschosses.
3. Fallback: 2.6 m (globaler Default, CONVENTIONS.md).

### 4.2 ID-Schema

```
"pw-<floorId>-gx-<counter>"    // GridRule, X-Achse
"pw-<floorId>-gy-<counter>"    // GridRule, Y-Achse
"pw-<floorId>-mx-<counter>"    // ModuleRule, X-Teilung
"pw-<floorId>-ct-<counter>"    // ConditionalThicknessRule
"pw-<floorId>-rl-<counter>"    // ReferenceLineRule
```

IDs sind sessionlokal (Zähler startet bei 0 je Modullade). Eingebrannte Wände
erhalten beim Commit neue stabile IDs über `uniqueId("W")` — konsistent mit dem
Wand-Werkzeug (vgl. [drawing-tools.md §8](drawing-tools.md#8-id-vergabe--immutabilität)).

### 4.3 Duplikat-Erkennung

`deduplicateWalls` vergleicht Start-/Endpunkte beider Wände (vorwärts: A→B == A→B,
und rückwärts: A→B == B→A) innerhalb einer Toleranz von 1 cm (0.01 m). Die **erste**
Instanz wird behalten; spätere Duplikate werden verworfen. Dies ist wichtig bei
Vollrastern (`"both"`), bei denen X- und Y-Wände exakt auf einem Rasterpunkt
zusammentreffen könnten.

### 4.4 Verhalten bei ungültigen Eingaben

| Situation | Verhalten |
|-----------|-----------|
| `spacing <= 0` oder `moduleSize <= 0` | `[]` |
| `referenceWallId` nicht in `existingWalls` | Fallback auf Bounding-Box, kein Fehler |
| Unbekannter `condition`-String | `matchesCondition` gibt `false` zurück (kein Treffer) |
| Unbekannter `SequenceRule`-Untertyp | TypeScript exhaustiveness-Guard, `[]` |
| Segment mit `|end - start| < 1e-6` m | Kann durch deduplicateWalls entfernt werden |

---

## 5. Integration ins Projekt

### 5.1 Ressourcen-Speicherung

`ParametricWall`-Einträge leben unter `Project.parametricWalls` (optionales Array).
Sie sind Teil des `.cad.json`-Dokuments und werden mit dem Rest des Projekts gespeichert.

```ts
// sampleProject.ts — Beispieleintrag
export const sampleProject: Project = {
  // …
  parametricWalls: [
    {
      id:                "pw-eg-raster",
      name:              "EG Längswände 3m-Raster",
      defaultWallTypeId: "wt-innen-15",
      rules: [
        { type: "grid", spacing: 3.0, directions: "x" },
        { type: "conditional-thickness", condition: "exterior",
          wallTypeId: "wt-aussen-36" },
      ],
    },
  ],
};
```

### 5.2 Rendering ohne UI (Phase A)

In Phase A werden parametrische Wände **nicht** automatisch gerendert. Der Auflöser
ist eine reine Funktion; Aufrufer müssen ihn explizit einbinden. Mögliche Verwendung
in `generatePlan` oder `Viewport3D`:

```ts
// generatePlan.ts (Ergänzung, Phase A)
const defaultWallType = project.wallTypes[0];
const extraWalls = (project.parametricWalls ?? []).flatMap((pw) =>
  resolveParametricWall(pw, activeLevelId, {
    floor: activeFloor,
    boundaryGeometry: projectBoundary,
  }, defaultWallType)
);
const allWalls = [...project.walls, ...extraWalls];
// … allWalls statt project.walls in der Rendering-Pipeline verwenden
```

### 5.3 Keine UI in Phase A

Kein Command, kein Panel, kein Formular. `ParametricWall`-Einträge werden in Phase A
ausschließlich **programmatisch** (Unit-Tests, `sampleProject`, direkte JSON-Bearbeitung
des Projekts) erstellt.

---

## 6. Ausblick: Folge-Phasen

### Phase B — UI und Command-Schnittstelle

- Neues Command (z. B. `PWWALL`) oder Ressourcen-Manager-Tab „Parametrische Wände"
  mit Formular-Editor je Regeltyp.
- „Einbrennen" (Flatten): `ParametricWall` → feste `Wall[]` in `Project.walls`
  einfügen und den `ParametricWall`-Eintrag entfernen (unidirektional, Undo über Store).
- Auswahl parametrischer Wände im Plan (als Gruppe); Grip-Editing der Raster-Parameter
  und Spannweiten.

### Phase C — Grid-Ressource und Schnittpunkt-Clipping

- `GridResource`: ein projektweites, benanntes Koordinatenraster (LV95-Offset,
  Rasterweite, Drehung), auf das mehrere `GridRule`-Instanzen via `gridId` verweisen.
- Präzises Clipping: erzeugte Wände werden am Gebäudeumriss getrimmt — exakte
  `lineIntersect`-Berechnung statt Bounding-Box-Approximation.
- Benutzerdefinierte Tags (`Wall.tags[]`) für komplexe `ConditionalThicknessRule`-
  Bedingungen jenseits von „exterior/interior/bearing".
- IFC-Export: `ParametricWall`-Gruppen → `IfcWallStandardCase` mit parametrischen
  Attributen und `IfcRelDefinesByType`.

---

## 7. Vollständige Anwendungsbeispiele

### 7.1 Schweizer Wohnbau: 3-m-Raster EG + 1.OG

Zwei-Geschoss-Wohnhaus, typisches CH-Wohnbauraster. Die Längswände beider Geschosse
entstehen aus zwei `ParametricWall`-Einträgen mit identischen Regeln, unterschieden
nur durch `floorId` beim Auflösen:

```
Top-down (Grundriss):

  y=12 ────────────────────────────  (W5)
  y=9  ────────────────────────────  (W4)
  y=6  ────────────────────────────  (W3)
  y=3  ────────────────────────────  (W2)
  y=0  ────────────────────────────  (W1)
       ↑
       x=0                       x=12
```

```ts
const rasterRegel: ParametricWall = {
  id:                "pw-laengswand-raster",
  name:              "Längswände 3m-Raster",
  defaultWallTypeId: "wt-innen-15",
  rules: [
    { type: "grid", spacing: 3.0, directions: "x" },
    // Außenwände (y=0 und y=12) erhalten den dicken Aufbau:
    { type: "conditional-thickness", condition: "exterior",
      wallTypeId: "wt-aussen-36" },
    // Außenwände: Achse liegt auf der Fassadenfläche:
    { type: "reference-line", referenceLine: "left", target: "exterior" },
  ],
};

// EG auflösen:
const wallsEG = resolveParametricWall(rasterRegel, "floor-eg",
  { floor: egFloor, boundaryGeometry: { boundary: rect(0,0,12,12) } },
  project.wallTypes[0]);

// 1.OG auflösen (gleiche Regel, anderes Geschoss):
const wallsOG = resolveParametricWall(rasterRegel, "floor-og1",
  { floor: ogFloor, boundaryGeometry: { boundary: rect(0,0,12,12) } },
  project.wallTypes[0]);
// Änderung spacing: 3.5 → beide Geschosse sofort konsistent.
```

### 7.2 Vollraster mit Außen/Innen-Differenzierung

Gebäudeumriss als Rechteck; die Randwände erhalten automatisch den dicken
Außenwand-Typ, alle anderen den dünnen Innenwand-Typ:

```ts
const vollraster: ParametricWall = {
  id:                "pw-eg-vollraster",
  name:              "EG Vollraster mit Differenzierung",
  defaultWallTypeId: "wt-innen-15",
  rules: [
    { type: "grid", spacing: 3.0, directions: "both" },
    { type: "conditional-thickness", condition: "exterior",
      wallTypeId: "wt-aussen-36" },
    { type: "conditional-thickness", condition: "interior",
      wallTypeId: "wt-innen-15" },
    { type: "reference-line", referenceLine: "left", target: "exterior" },
  ],
};
```

```
  ─────┬─────┬─────┬─────
  │    │     │     │    │
  ─────┼─────┼─────┼─────
  │    │     │     │    │
  ─────┴─────┴─────┴─────

  Rand-Segmente: wt-aussen-36 (dicker Aufbau)
  Innen-Segmente: wt-innen-15 (dünner Aufbau)
```

### 7.3 Modulbauweise: 6-m-Joch, Bürogebäude

Längliches Bürogebäude, 36 m × 12 m, 6-m-Joch. Querwände entstehen automatisch;
Entwurfsänderung (z. B. auf 7.2-m-Joch) erfordert eine einzige Zahl:

```ts
const joch: ParametricWall = {
  id:                "pw-buero-joch",
  name:              "Büro 6m-Joch",
  defaultWallTypeId: "wt-beton-20",
  rules: [
    {
      type:       "module",
      moduleSize: 6.0,
      direction:  "x",   // Querwände senkrecht zur X-Achse
      // referenceWallId: "W-sudfassade" → Spanne aus Referenzwand
    },
  ],
};

// resolveParametricWall → Querwände bei x = 6, 12, 18, 24, 30
// (bei Bounding-Box minX=0, maxX=36, Enden selbst ausgespart)

// Änderung auf 7.2-m-Joch: moduleSize: 7.2
// → 4 Trennwände bei x ≈ 7.2, 14.4, 21.6, 28.8 — automatisch neu berechnet.
```

---

## 8. Architektur-Garantien

- **Modell bleibt einzige Wahrheit.** `ParametricWall`-Definitionen sind Daten in
  `Project.parametricWalls`; `resolveParametricWall` ist eine **reine Funktion** ohne
  Side-Effects. Keine globale Laufzeit-Geometrie-Schicht.
- **Erzeugte Wände sind gewöhnliche `Wall`-Objekte.** Alle nachgelagerten Systeme
  (`generatePlan`, `Viewport3D`, `computeJoins`) arbeiten unverändert; sie müssen
  nicht zwischen „parametrisch erzeugten" und „direkt gezeichneten" Wänden
  unterscheiden.
- **Fehlertoleranz statt Absturz.** Unbekannte Regeltypen liefern `[]`; der TypeScript-
  exhaustiveness-Guard fängt fehlende `case`-Zweige zur Compilezeit. Unbekannte
  Bedingungsstrings in `ConditionalThicknessRule` geben `false` (kein Treffer) statt
  zu werfen.
- **Keine vorzeitige Generalisierung.** Phase A liefert fünf Regel-Varianten und
  einen Auflöser. UI, Command-Schnittstelle und Grid-Ressource folgen in Phase B/C.
- **Immutabilität.** Verfeinerungsregeln (`ConditionalThicknessRule`,
  `ReferenceLineRule`) geben modifizierte **Kopien** zurück; `existingWalls` werden
  nie mutiert — konsistent mit der `setProject`-Konvention (CONVENTIONS.md).
