# STATUS – wo stehen wir? > **Übergabe-Dokument zwischen Arbeits-Sessions.** Jede Session (Mensch oder > KI) aktualisiert dieses File nach jedem abgeschlossenen Schritt: Was ist > fertig, was ist in Arbeit, was ist als Nächstes dran, welche bewussten > Abweichungen gibt es. Meilenstein-Definitionen: Brain → > `vault/50_Umsetzung/Bauplan-Meilensteine.md`. **Stand: 2026-09-30** > ℹ️ Die Arbeiten 2026-07-16 bis 2026-08-04 (Farbwelt 16→33 Slugs, +28 Möbel > inkl. Vorfilter-Passung, L-Sofa 2D=3D, Viewer-Fixes, Kurator v3.3 mit > schlankem Repair und erkannten Abschneidungen) sind nicht als eigener > Abschnitt hier, sondern als Learning im Brain zusammengefasst: > `vault/10_Learnings/Learning-Kurator-v33-Katalog-Varianten-Tokenbudget.md` > (Details: Commits `3f3c1ea` … `85a71a3`). ### Scan ohne LiDAR: Video → MapAnything im Colab-Worker (2026-09-30, ADR-0016) Bryans Vorgabe: Scan **muss ohne LiDAR** gehen (USP). Recherche: keine Gratis- iOS-App exportiert ohne LiDAR Video + Posen (Stray Scanner nur LiDAR, NeRFCapture nur Einzelbilder) → AR-Posen-Pflicht aus ADR-0012 fällt. - **Neuer Standardweg** in `services/scan-worker`: normales Video → `frames` (Schärfe = Laplace-Varianz, zeitbasiert N Keyframes/s, Default 3, verwackelte < 35 % Median raus, max. 120) → **MapAnything** (`facebook/map-anything-apache`, Apache) → `rekonstruktion` (Hochachse aus Bild-oben aller Kameras, gedrehtes Video erkannt, Verfeinerung per Bodenebene im unteren Höhenband + SVD-Fit ≤ 10°, Boden → z = 0, Diagnose Raum-/Kamerahöhe) → optional Massstab aus bekannter Raumhöhe → `fuse_mit_farben` (2 cm) → PLY → SpatialLM. GPU-Speicherfehler → Wiederholung mit halb so vielen Frames. AR-Posen-Weg bleibt (`.zip` mit `poses.json`). - **SpatialLM in eigenem venv** (`FP_SPATIALLM_PYTHON`, pinnt torch 2.4.1), MapAnything im Colab-Python; GPU-Speicher dazwischen freigegeben. Notebook `colab_worker.ipynb` entsprechend (Setup, Regler, Filmtipps). - **Tests:** scan-worker 22 → 41 (Frame-Auswahl; synthetischer Raum in beliebig gedrehtem Weltsystem inkl. gedrehtem Video und 4° Handy-Kippung; Verdrahtung mit gestubbten GPU-Teilen inkl. Speicher-Rückfall). **Nicht getestet: echter Colab-Lauf** (Installation, T4-Speicher, Qualität). - **Als Nächstes:** Bryan startet Colab mit den alten R1-Videos (erster Lauf = Gate für Installation/Speicher) → R1 neu filmen (normale Kamera-App) + Restmasse → Messung gegen Ground Truth → Learning. Danach M7: Massstab- Bestätigung im Korrektur-Modus, Live-Weg als Job + Polling. ### Vercel-Eingang Phase 2 – erster Deploy grün (2026-09-29) - **Preview live** (Workflow «Deploy Vercel» Lauf #8, Branch `claude/keen-meitner-0qijkc`): Build 14 s, Upload 20.8 MB, Vercel-Projekt `bryan-125a/fp-poc`. Produktions-Domain ist **`fp-poc-seven.vercel.app`** (`fp-poc.vercel.app` war vergeben; in Vercel → Domains änderbar). - **Token-Odyssee** (Läufe #1–#7, «Could not retrieve Project Settings» bzw. «User not found (404)»): IDs waren korrekt, der Token nicht nutzbar. Gelöst mit neu erzeugtem persönlichem Token (Account Settings → Tokens, Scope «Full Account»). Dafür neuer Workflow-Schritt «Vercel-Konfiguration prüfen» (Leerraum, `prj_`/`team_`-Präfixe inkl. Vertauschung, Token-Länge, `project ls` im Team) – bleibt drin, spart beim nächsten Mal die Suche. - **Brain nachgeführt:** ADR-0015, Learning Vercel-Eingang, `POC-Demo-Architektur-HF`, Scan-Fahrplan («Job + Polling Pflicht»). - **Als Nächstes:** Bryan testet die Preview am Handy (Checkliste: Start/Weck- schirm, Bad/Wohnen/Küche + Vorschlag, Swipe-Fotos, Export, Scan-Bundle, Vergleich HF). Danach Branch → `main` = erster Produktions-Deploy (wartet auf `/api/health.build`). Offen: 27-MB-Upload durch den Rewrite + Kaltstart live. ### Vercel-Eingang Phase 1 – Fundament für zwei Frontends (2026-09-28) Ziel (mit Bryan besprochen, Variante «K5»): Frontend ZUSÄTZLICH auf Vercel (`fp-poc-seven.vercel.app`), `/api/*` per Rewrite an den HF-Space; der Space bleibt vollständige Referenz (Frontend + API wie bisher). Phase 1 = alles, was beide Eingänge brauchen – wirkt schon jetzt auf HF. Abwägung K1–K7 (Timeouts, 4.5-MB-Body, Kontingente, Nutzerreise): kommt als ADR-0015 ins Brain. - **Swipe-Bilder statisch** unter `/bilder/...` aus dem Build: Vite-Plugin `fpBilder()` (`vite.config.ts`) – Dev-Middleware mit Path-Traversal-Schutz, Build kopiert die 114 Fotos nach `dist/bilder/` (nur beim echten Build). `Stil.tsx` baut URLs über `bildUrl()` (segmentweise kodiert – Dateinamen mit Leerzeichen/Klammern). `/api/bilder` bleibt als Rückfall bestehen. - **Weck-Logik** (`backend.ts` + `api.ts`): HTML statt JSON (HF-Ladeseite eines schlafenden Space, auch mit Status 200; Proxy-Fehlerseite) oder Netzfehler → EIN gemeinsamer Weckvorgang (`/health`-Pings 2/3/5 s …, max. 3 min) → Anfrage genau einmal wiederholt (unbedenklich: alle Endpunkte ohne Server-Zustand). App zeigt Ladezustand-Variante `wecken` bzw. Fehlerkarte «Erneut versuchen»; der Hinweis «pnpm api starten» erscheint nur noch im Dev-Modus. - **Kurator-Gesamtdeadline** `FP_KURATOR_DEADLINE_S` (Default 90 s, `0` = aus) über alle LLM-Calls inkl. Repairs und 429-Backoff: unter 5 s Restzeit kein neuer Call, HTTP-Timeout auf Restzeit gekürzt, ein Backoff, der das Budget sprengen würde, bricht ab → ehrlicher (Teil-)Fallback + Marker `CURATOR_DEADLINE`. Die ungültige Antwort wird jetzt VOR dem Repair gesichert → die Platz-Rettung greift auch, wenn für den Repair keine Zeit mehr bleibt. Grund: Vercel-Rewrites warten max. 120 s aufs erste Byte. LLM-Eval-Workflow: Deadline aus (misst KI-Qualität unter Drosselung, nicht Fallbacks). - **API:** `Cache-Control: no-store` auf allen Antworten ausser `/bilder` (Middleware liest den Pfad VOR `call_next` – Starlette mutiert `root_path` im Mount); `/health` meldet additiv `build` (Git-SHA aus `BUILD_SHA`, schreibt `deploy-space.yml`; `git add -f`, weil die mitkopierte `.gitignore` sie sonst im Deploy verschluckt); `.webmanifest`-MIME in `space.py`. - **PWA:** `manifest.webmanifest` («FP POC», standalone, Theme #243D35), Icons 180/192/512/512-maskable aus dem Signet (deckend weiss, sonst füllt iOS schwarz), Meta-Tags in `index.html`. Bewusst KEIN Service Worker. - **Tests:** web 223→243, engines 434→454 (Deadline mit simulierter Uhr, Weck-Logik mit gemocktem fetch + Mutationstest). E2E-Smoke `space.py` lokal: Manifest/Icons/`/bilder`/`/api/bilder`/`/api/health` wie erwartet. - **Phase 2 (Code fertig, wartet auf Bryans Vercel-Projekt + Secrets):** `vercel.json` (Build aus dem Monorepo-Root, Output `apps/web/dist`, Rewrite `/api/*` → `bryan-hslu-fp-poc.hf.space/api/*`, Git-Deploys AUS) + `deploy-vercel.yml`: Produktion erst, wenn `/api/health.build` = Commit-SHA (max. 20 min, `cancel-in-progress` spart Actions-Minuten), Previews für andere Branches; gebaut per `vercel pull/build/deploy --prebuilt` auf dem Runner (CLI 60.1.3 gepinnt, gleiche Toolchain wie CI, Commit-Autor egal). Ohne Secrets übersprungen. Lokaler `vercel build` geprüft: nur statisch (22 MB, 114 Bilder, Icons, Manifest), KEINE Functions, Routing = filesystem → Rewrite. README «Deploy» + CLAUDE.md §11 nachgeführt. - **Als Nächstes:** Bryan legt Projekt `fp-poc` an + 3 Secrets → Preview-Test (27-MB-Upload durch den Rewrite, Kaltstart live, Handy-Durchgang auf beiden URLs) → Produktion; danach ADR-0015 + `POC-Demo-Architektur-HF` + Scan-Fahrplan «Schritt 5 = Polling Pflicht». ### LLM-Läufe real: Kurator antwortet echt – Groq-Debugging-Saga (2026-07-15) 6 Workflow-Läufe (`kurator-llm-eval.yml`, Secret FP_KURATOR_API_KEY) bis zur ersten vollständigen echten KI-Antwort. Fehlerkette (je Lauf diagnostiziert): 1. **413**: Call-A-Prompt zu gross (volle achsenTags-JSON je Kandidat) → v3.2 Prompt-Diät (Kompakt-Stil-Notation, Kandidaten-Budget, adaptive Kürzungs-Leiter, FP_KURATOR_MAX_PROMPT_TOKENS). 2. **413 trotz Diät**: Groq zählt Prompt + MAXIMALES Antwort-Fenster; ohne max_tokens wird das volle Kontextfenster angenommen → v3.2.1 max_tokens explizit (FP_KURATOR_MAX_ANTWORT_TOKENS) + 429-Backoff. 3. **400 json_validate_failed**: Qwen3 denkt DEFAULT laut, -Präfix bricht response_format=json_object, der gescheiterte Call verbrennt das Minutenbudget → v3.2.2 reasoning_effort=none für Qwen3 (env-übersteuerbar). 4. **429**: Free-Tier TPM (Qwen 6k, Llama 12k) drosselt Serien → v3.2.3 Backoff 3×/30s + FP_EVAL_PAUSE_S zwischen Läufen. **Ergebnis (Llama-3.3-70b-versatile):** 3/9 Diagnose-Kombis mit VOLLER echter Antwort, alle 6 Plausibilitäts-Ampeln grün – z.B. wohnen×mittig ohne jeden Marker: Konzept (Eiche+Leinen, erdig), 4 Stühle zum Esstisch, Farben je Item, Boden parkett-eiche, Wände putz-warm + EINE Täfer-Akzentwand. Rest-Fallbacks nur noch 429 (Budget). 413/400 mit Llama komplett weg. **Empfehlung:** Space-Secret FP_KURATOR_MODEL auf llama-3.3-70b-versatile – Einzelnutzer-App (3 Calls/Plan) liegt locker im 12k-TPM-Budget. Qwen3-Pfad bleibt funktionsfähig (Thinking default aus). Offen: Eval-Gate-Vergleich fair machen (9 vs 45 Läufe), Norm-Korrektur-Rate erst mit mehr echten C-Calls aussagekräftig. ### Welle C – UI-Trio + Diagnose/LLM-Workflow + Norm-Verifikation (2026-07-14/15) - **UI-Trio:** Einzelwand-Materialwahl (`apps/web/src/wandauswahl.ts`, Panel- Wandliste 1-basiert angezeigt, 2D-Highlight, Norm weiterhin über `/flaechen/pruefen`) · **«3 Vorschläge»-Karten** in SchrittVorschlag (Mini-2D readonly + Kennzahlen; `/solve` `variantenDetails` → additiv `variantenPlaene` best-first) · **2D-Flächen-Layer** (Default AN, gleiche Auflösung wie 3D via `flaechen2d.ts`, Legende; AUS = alte Darstellung, testbelegt). Offen: Relations-Score in Karten roh/negativ → normalisieren. - **Merge-Erkenntnis C×D:** Begehbarkeits-Leiter in `varianten._waehle_begehbar` zentralisiert — der Karten-Kopf ist IMMER der gelieferte begehbare Plan (auch wenn nicht Score-Sieger), Rest best-first; Test präzisiert. Dabei `by_id`-NameError im Reduktions-Zweig gefixt (Folge des Parallel-Refactors). - **Diagnose-Harness** `scripts/kurator_diagnose.py` (ADR-0014): Raum×Profil- Matrix, echte Kurator-Antworten + 6 Plausibilitäts-Ampeln; Baseline 45/45 ohne ⚠️, byte-deterministisch. **LLM-Lauf:** Workflow `.github/workflows/kurator-llm-eval.yml` (Runner haben Netz zu api.groq.com, Remote-Sessions nicht; Thinking per Input `reasoning`) — **wartet auf Repo-Secret `FP_KURATOR_API_KEY`** (Klartext-Key als Input wird vom Secret-Scanning korrekt blockiert). - **Flächen-Normregeln verifiziert:** Küchen-Spritzzone 1.2→**1.5 m** (Fliesenspiegel-OK ~1.5 m ab Boden), Nasswand 2.0 m bestätigt (DIN 18534: ≥20 cm über Brausekopf), Rutschhemmung Ziel bfu GB2/B (erst mit Produkt- daten prüfbar). Quellen: Brain `Quellen-Flaechen-Normen`. ### Welle D – softScore bad/wohnen echt + Begehbarkeit hart am Planende (2026-07-15) Zwei Qualitätspunkte aus dem v3-Fund («softScore bad/wohnen immer 0.0») und dem circulation-Learning (Metrik vertrauenswürdig, Hürde war nur Hot-Path-Performance). - **softScore stil/relation echt befüllt (alle Raumtypen konsistent):** Muster des Küchen-Ergonomie-Patches – der Domänen-Solver schreibt NACH dem Interpreter-Lauf in die vorhandenen Felder; Interpreter/Schema/Goldens unberührt (Paritäts-Gesetz aus dem Weg). `stil` = mittlerer Stil-Score der platzierten Items (Wiederverwendung `kurator.stil_score`, kein Duplikat); `relation` = Anteil erfüllter relationaler/Anordnungs-Wünsche am fertigen Plan (gleiche Quelle `baue_rel_map` wie Platzierung + K-Scoring, binäre v0-Urteile `solver._relation_erfuellt`, ohne Wünsche vakuum-wahr 1.0); `ergonomie` bleibt Küchen-spezifisch (Arbeitsdreieck). Beispielwerte bad-sample seed 1 mit Profil: stil 0.14 · relation 1.0. - **Begehbarkeit hart am Planende (Weg «hart am Ende», nicht im Filter):** Messung: circulation kostet das 10–13-fache ALLER harten Regeln (~15 ms Bad / ~33 ms Wohnen vs. 1.4–2.6 ms) bei 22–47 Feasibility-Aufrufen je solve ⇒ hart im Filter wäre ~7–10× langsamer; zudem globale Grid-Eigenschaft, nicht inkrementell. Regel-JSON bleibt **soft** (Parität/Goldens byte-identisch); die Härte lebt in der Liefer-Schleife: `solver.solve_begehbar` (API-Pfad ohne Varianten) prüft den fertigen Report, bei Verletzung deterministischer Re-Solve mit reduzierter Auswahl (letztes P3-/Ergänzungs-Item ohne Anker-Pflicht, max. 3), danach VOLLER Plan mit sichtbarem Hinweis («BEGEHBARKEIT NICHT ERREICHT» im circulation-`ruleResult.hinweis` – constraintReport ist additionalProperties:false, darum kein neues Feld). K-Varianten (`loese_mit_varianten`): Leiter = nächstbeste BEGEHBARE Variante → Reduktions-Re-Solve → Hinweis; Massnahme additiv in `varianteInfo.begehbarkeit`, Rangfolge sonst byte-identisch (dokumentiert + getestet). Küche: letzte P3-Deko-Platzierung entfernen (max. 3) → Hinweis. - **Tests:** neues `test_begehbarkeit.py` (54 Tests): Property ⭐ «jeder gelieferte Plan begehbar ODER sichtbarer Hinweis» über bad/wohnen/kueche × Seeds + Varianten-Pfad, 0-❌-Invariante, Determinismus beider Leitern, echter unfixbarer Bestands-Engpass (Hinweis-Pfad), Reduktions-Mechanik, softScore-Diskriminierung/Defaults. `test_varianten`/`test_solve_endpoint` «best ≥ v0» präzisiert (gilt, ausser die dokumentierte Leiter greift). - **Wirkung (echter Evaluator, seeds 0–11):** bad-sample 8 direkt ok · 1 durch Reduktion · 3 Hinweis; wohnen-sample 6 · 1 · 5; Varianten-Pfad wohnen: alle Seeds begehbar (meist via «andere-variante»). Küche unverändert (knapp/ok). - **Bewusst offen:** Regel-`hinweis`-Text in `data/rules/basis.json` erwähnt noch «Hochstufung braucht Solver-Support» – Textupdate würde die 3 goldenen Paritäts-Fixtures anfassen (dort eingebettet), bewusst verschoben. Learning- Loop-Notiz ins Brain (Weg-1-Entscheid + Messzahlen) steht aus. ### Kurator v3.1 Welle A – Objekt-Ebenen + Anzahl + Thinking (2026-07-14, ADR-0014) Vorgabe: Brain `ADR-0014-objekt-ebenen-und-kurator-kontrolle` + `Objekt-Ebenen-Modell`. Eiserne Regel: jede neue KI-Freiheit ↔ deterministische Kontrolle. - **Schemas (additiv):** `katalog-item` bekommt `objektEbene` (haupt|ergaenzung), `ankerTyp`, `maxAnzahl` (1..6, Default 1). `kurator-vertrag` v0.6: Response `hauptObjekte` + `ergaenzungen[{itemId,anzahl}]` (beide optional); `auswahl` bleibt Pflicht + expandiert (jede itemId genau einmal). Codegen TS+Python neu. - **Daten:** alle 127 Katalog-Items getaggt (bad 11 haupt/29 erg · wohnen 12/28 · kueche 38/9). **2 neue Esszimmerstuhl-Items** (wohnen, P2, ergaenzung, ankerTyp esstisch, maxAnzahl 6, `near:esstisch:1.3`) – das 3D-Kit `stuhl` + 2D-Symbol existierten bereits (wiederverwendet, kein Duplikat). Neu `data/kurator/anzahl-leitplanken.json` (Instanz-Korridor je Raumtyp/Fläche). - **Kurator:** Call A zweistufig (`hauptObjekte` zuerst, dann `ergaenzungen` mit Anker+maxAnzahl je Slot; Anzahl-Leitplanke aus Daten gerendert). Harte Kontrollen `_validiere_ebenen`: Auswahl⊆Kandidaten · P1-Pflicht in haupt · anzahl 1..maxAnzahl · Anker-Haupt-Pflicht · Platz-Budget über alle Instanzen. WEICH: Korridor (1 Repair, dann Marker `CURATOR_ANZAHL_AUSSERHALB`). Baseline zweiphasig (haupt → verankerte Ergänzungen, Stühle det. bis 4, Korridor-Cap). Thinking: `FP_KURATOR_REASONING` (→ reasoning_effort + reasoning_format hidden, nur bei Env) + `FP_KURATOR_TEMP`. Alte Kataloge ohne `objektEbene` brechen nicht. - **Solver/API:** `solve()`/`loese_mit_varianten()` neuer `mengen`-Param (itemId→anzahl → n Placements, eindeutige IDs, `near:esstisch` je Instanz, Determinismus unberührt). `/solve` optionales `mengen`; Baseline-Pfad leitet es aus `ergaenzungen` ab (`mengen_aus_antwort`, kein Vertrags-Extra-Feld). Client: `mengenAusKurator` → /solve. K-Varianten reichen mengen durch. - **Eval:** Überlebensrate zählt jetzt Instanzen; neue Spalte «Anker-Erfüllung». Baseline-Report neu (0.84 Überleben / Anker 100 % / 0 ❌ in 45). - **Bewusste Abweichungen / offen:** (1) Korridore ggü. ADR-Beispiel etwas höher gewählt, weil Stühle jetzt als Instanzen zählen. (2) Stuhl-Distanz 1.3 m (0.8 m aus der ADR-Skizze kollidiert mit dem 1.4×1.2-Tisch). (3) **Doppelzählung Esstisch↔Stühle:** die Esstisch-Items heissen «(inkl. 4 Stühlen)» und Preis/2D deuten Stühle an – jetzt kommen zusätzlich echte Stuhl-Instanzen. Bewusst NICHT angefasst (Naming/Preis/Rendering = grösserer Eingriff) → Bryan-Entscheid, ob Esstisch entkoppeln (umbenennen/entpreisen) oder Stühle nur als eigene Instanzen. - Gates grün (lint/typecheck/test/schema-check); NICHT committet (Orchestrator). ### Kurator-Pipeline v3 – 5 Wellen (2026-07-13, ADR-0013) Vorgabe: Brain `Kurator-Pipeline-v3-Konzept` + `ADR-0013-kurator-pipeline-v3`. Eiserne Regel: jede neue KI-Freiheit ↔ neue deterministische Kontrolle. - **W1 Kohärenz/Norm:** Call A liefert zuerst `konzept` (Vertrag v0.4), Konzept → B+C; Stilprofil neu in B, Auswahl neu in C; **Platz-Budget hart** in `_validiere` (Daumenregel wie Baseline, Unsatisfiability-Guard); **Norm-Kontext aus `data/rules/flaechen.json` pro Raum gerendert** (`norm_kontext_flaechen`: konkrete Nasswand-Indizes; Norm-Prosa aus der Prompt-.md entfernt – eine Quelle); neu `kueche-wand-spritzzone` (1.2 m, zu-verifizieren); Relations-Ziel-Validierung; Few-Shots; temp 0.3 + seed. - **W2 Daten:** alle 125 Katalog-Items volle 8 Achsen + `farbVarianten` (16 farbSlug-Enum im Katalog-Schema); 3 falsch getaggte Wohnen-Bilder + Eckschrank-Copy-Paste-Tags korrigiert. - **W3 Farben (KI+UI):** Vertrag v0.5 `farben` (itemId→farbSlug, Cross-File- `$ref` aufs Katalog-Enum – Codegen löst auf, kein Duplikat; 2 Testdateien auf Registry-Validierung umgestellt); `placement.farbe` (Plan additiv); eigener Validierungsschritt + Repair + Bereinigung (CURATOR_FARBEN_BEREINIGT); Client `farben.ts` (MANUELL>KI>Default, Default = erste farbVariante); Farb-Picker am selektierten Objekt + Flächen-Material-Picker; manuelle Flächen laufen durch `POST /flaechen/pruefen` (Python bleibt Norm-Quelle). Bewusst: Wand-Material gilt für alle Wände (keine Einzelwand-Selektion). - **W4 Eval:** `kurator_eval.py` = 3 Räume × 3 Profile × 5 Seeds; Metriken Überlebensrate/Auslastung/Palette-Treffer/Norm-Korrektur-Rate/Invariante; deterministischer Report `services/engines/reports/kurator_eval.json`. **Baseline-Messlatte: 0.867 / 0.886 / 0.855, 0 ❌ in 45 Läufen.** LLM-Lauf mit FP_KURATOR_URL wiederholen → muss Baseline schlagen (Gate). - **W5 K-Varianten:** `varianten.py` (K=3 Sub-Seeds deterministisch, bestes lexikografisch: platziert → −knapp → Relations-/Stil-Score → Index); `/solve` Flag `varianten` (Default false), App nutzt true (Bad/Wohnen; Küche ausgenommen). **Fund:** `constraintReport.softScore` ist bei bad/wohnen immer 0.0 (nur Küche patcht ergonomie) → knapp-Zähler ist das echte Soft-Signal. Offen: LLM-Eval-Lauf; «3 Vorschläge zeigen» (UI-Bonus); Einzelwand-Material-Selektion. ### CI-Fix + 2D-Wand-Semantik + 3D-Polish + Ladebildschirme (2026-07-13) - **CI grün:** Prettier-Format der Codegen-Ausgabe gefixt; Merke: Gates immer mit den ROOT-Kommandos fahren (`pnpm lint/typecheck/test` deckt alle Workspaces — apps/web allein reicht nicht). - **2D-Wand-Semantik korrigiert:** `floor.polygon` = INNENKANTE (nutzbare Raumfläche, Solver-Realität) → Wand wächst jetzt nach AUSSEN (`aussenPolygon`, Gehrung aussen); Boden = volles Polygon. - **3D-Rendering-Polish:** prozedurale Environment (Lightformer-Softbox, offline/CSP-fest), ACES-Tonemapping, ContactShadows; Chrom echt spiegelnd, Glas reflektierend. Kein Post-Processing (bewusst schlank). - **Ladebildschirme:** FP-Logo + Puls + Fortschrittsbalken an allen 4 Wartepunkten (v.a. Vorschlag/Kurator mit 3 KI-Calls); Piktogramme +20–30%. ### Kurator-Pipeline v2 + Licht + individuelle Flächen (2026-07-12) - **Kurator v2 – 3 KI-Calls** (`kurator.py`, Vertrag v0.2 additiv): A Auswahl · B **Anordnung** (je Item wandIndex/relationen/prioritaet; Solver setzt sie WEICH um – Ranking, nie NoFeasiblePlacement wegen Wandwunsch) · C **Flächen** (materialSlug-Enum 10 Slugs als einzige Quelle, bereich voll/halbhoch/sockel, hoeheM, akzent). Jeder Call validiert + 1 Repair + **Teil-Fallback** (B→relationalRules, C→None). Prompts: `data/prompts/kurator-{rolle,anordnung, flaechen}.md`. Norm-Filter/Parität unangetastet → 0 ❌ bleibt beweisbar. - **Licht & Lampen** (Dressing): Platzierungsart `an_decke` (Schema-Enum additiv); Pendelleuchte (wohnen+kueche, über Esstisch/Arbeitsfläche), Tischleuchte (wohnen) – leuchten echt (Emissive + pointLight). Offener Punkt: glTF-Upgrade einer Leuchte braucht das Punktlicht noch (additiv nachrüstbar). - **3D-Flächen-Rendering**: Kurator-`flaechen` sichtbar – je Wand eigenes Material (raumhoch/halbhoch/Sockel, Akzentwand), Boden aus Slug; ohne `flaechen` exakt bisheriges stilabgeleitetes Verhalten. Offen: Zusammenspiel Kurator-Flächen vs. manuelle Oberflächen-Variante definieren; 2D zeigt die Flächen (bewusst) noch nicht. - HF: /plan macht jetzt bis zu 3 Groq-Calls → wenige Sekunden länger; Fallback- Marker je Call in der Begründung (`CURATOR_ANORDNUNG_FALLBACK` etc.). - **Flächen-Normkontrolle (Nachtrag):** `data/rules/flaechen.json` (deklarativ, getrennt vom Paritäts-Set) prüft jeden KI-Flächen-Output hart: Bad-Boden wasserfest, Nasswände ≥2.0 m wasserfest (Heuristik: Fixpunkte ≤0.5 m), Küchenboden abwaschbar. Ablauf: Regeln VORAB im Prompt → pruefe_flaechen → 1 Norm-Repair (`…NORMREPAIR`) → deterministische Korrektur (`…NORMKORREKTUR`). Vokabular +tapete-hell/+taefer-holz (Wohnräume: Putz/ Tapete/Täfer statt Fliesen). Richtwerte «zu-verifizieren» (SIA 271/DIN 18534). ### Katalog-Ausbau: 13 neue Möbeltypen (2026-07-12) - **26 neue Katalog-Items** (je 2 Stil-Varianten, alle P3): Bad waeschekorb/ badhocker/abfalleimer/midischrank · Wohnen pouf/vitrine/konsolentisch/ barwagen/recamiere · Küche barhocker/servierwagen/vorratsschrank/ weinkuehlschrank (elektro). relationalRules nutzen die neue Grammatik (group:sitzgruppe, against-wall, near, corner). - **Voll dargestellt:** 13 volumetrische 3D-Kits (`moebel3d.tsx`) + 13 2D-Plan-Symbole (`symbole2d.ts`) + MATERIAL_FARBE. - **Baseline-Fix** (`kurator.py`): Rest-Slots nach priorityClass P1→P2→P3 statt alphabetisch (latenter Bug: P3-Deko konnte Kernmöbeln die Flächen-Daumenregel aufbrauchen). Solvability-Regression: alle 10 Raum-Fixtures × Seeds 0–5 → 0 ❌, kein NoFeasiblePlacement (kleine Räume lassen P3 erwartungsgemäss weg). - **Offen:** weitere Wunschlisten-Typen (z.B. Dusch-WC, Raumteiler, Kücheninsel als eigenes Möbel) bei Bedarf nach demselben Muster. ### 3D-Volumetrie + Scene-Dressing für ALLE Raumtypen komplett (2026-07-09) - **Modelle** in höherer Volumetrie (rundbox/lathe/torus + Glas/Chrom) für **Bad, Wohnen UND Küche** – alle `moebel3d`-Bauer klar erkennbar, nicht mehr schematisch; Ampel-/Solver-Logik unberührt, bbox-Invariante bleibt. - **Scene-Dresser** für alle drei Raumtypen: `data/dressing/{bad,wohnen,kueche}.json` + Kits in `dressing3d-kits.ts`, generischer Loader/Engine, „Deko"-Toggle. Sparsam, „frisch gebaut". Verändert Plan/`constraintReport`/LV NICHT (0 ❌). - **Offen/Backlog:** komplett neue Möbeltypen aus Bryans Wunschliste (Recamiere, Weinkühler, Doppelwaschtisch, Barwagen …) = eigener Schritt (Katalog+Regeln+ Solver). Deko-Sonderfall „an Gerätegriff hängen" bräuchte neue Platzierungsart. Echtes Spiegel-Chrom bräuchte Environment-Map. Bryans echte `.glb` ersetzen Platzhalter über `apps/web/public/assets/dressing/`. ### 3D-Modelle Bad – höhere Volumetrie (2026-07-09) - **Primitiv-System erweitert** (`apps/web/src/moebel3d.tsx`): neben box/zyl/kugel neu **`rundbox`** (RoundedBox), **`lathe`** (Rotationskörper), **`torus`** – plus Material-Rollen **`glas`** (echt transparent) und **`chrom`** (metallisch). Ampel-Farblogik unangetastet (Rolle steuert nur Oberflächen-Physik). - **Alle Bad-Objekte + 5 Deko-Kits** auf klar erkennbare, plastische Volumetrie gehoben (WC/Lavabo/Dusche als Flagship, dann Fan-out: Badewanne, Spiegel, Badmöbel, Hochschrank, Regal, Handtuchstange/-heizung, Urinal, Bidet, Badteppich, Wandleuchte, Pflanze; Deko: Seifenspender, Zahnputzbecher, gefaltete Handtücher, Tray, Pflanze). `dressing3d.tsx`-Renderer auf die neuen Primitive erweitert. bbox-Invariante (`passtInBbox`/`clampTeil`) bleibt. - **Offen:** Fan-out Wohnen/Küche-Modelle im gleichen Detailgrad; echtes Spiegel-Chrom bräuchte eine Environment-Map (aktuell mattes Metall). ### Scene Dressing – Bad-Durchstich (2026-07-09) - **Neue visuelle Deko-Ebene**, client-seitig & getrennt vom Plan (Konzept: Brain `vault/50_Umsetzung/Scene-Dressing-Konzept.md`). `sceneDressing()` in `apps/web/src/dressing.ts` (Seed/Anker/Stil/Klasse-B-Kollision), gerendert als `DressingLayer3D` mit **„Deko"-Toggle** im 3D-Viewer. Verändert `placements`/`constraintReport` NICHT → 0 ❌ unberührt (Regressions-Test). - **Ästhetik „frisch gebaut, nicht bewohnt":** 5 sparsame Bad-Objekte (Seifenspender, Zahnputzbecher, Tray, gefaltete Handtücher, Pflanze). - **Asset-Drop-in:** `apps/web/public/assets/dressing/.glb` (README dort); `assetStatus:"modeled"`+`gltfRef` → ersetzt Platzhalter automatisch. Bryan modelliert + lädt hoch, Rest macht die App. - Bugfix: Ecken-Footprint (rotiert) stach durch die Wand → inkrementell nach innen rücken. Details: `vault/10_Learnings/Learning-Scene-Dressing-Bad-Durchstich.md`. - **Offen:** Fan-out Wohnen/Küche; Klasse C (`an_wand`) in Engine da, ungenutzt. ### Zuletzt (2026-07-08, diese Session) - **UI-Feinschliff:** Schein nach innen stärker & überall, mehr CI-Grün, Piktogramme überall grösser (`fp.css`/`theme.ts`/`Piktogramm.tsx` + Ansichten). - **Solver/Kurator «Stufe 1»:** reiche Anordnungs-Grammatik (`near`, `against-wall`, `corner`, `facing:`, `opposite:`, `group:`, `pair-with:`) als weiche Constraints in P2/P3 + **stil-abhängige Platzierung** (v0: Achse `raumgefuehl` → Wand-Nähe, kleines Gewicht). Harter Norm-Filter/Interpreter/Parität unangetastet → 0 ❌ bleibt beweisbar. Neu: `services/engines/src/fp_engines/relationen.py`; `solve()` um `style_profile` additiv erweitert; `kurator-rolle.md` lehrt die Grammatik; `wohnen.json` demo. **Offen (Kalibrierung):** welche Stilachsen welchen Platzierungs-Bias steuern → Brain-Learning `vault/10_Learnings/Learning-Solver-Stufe1-Anordnung-Stil.md`. - **2D-Grundriss CAD-Look:** Wand-Aussenkante liegt auf der Raumlinie, Stärke wächst nach innen, Gehrungs-Ecken (`innenPolygon`/`wandBaender` in `plan2d.ts`); Öffnungen sitzen im Band. - **Objekt-Panel erweitert:** `ObjektInfoPanel.tsx` – Kosten + Wechsel-Alternativen (Thumbnail = 2D-Symbol, Preis, Masse, Stil-Match). Produktfotos je Item fehlen noch (Asset-Pipeline-Thema) → Thumbnail als Austauschpunkt vorbereitet. - **(läuft)** mehr Beispielräume (`packages/shared/fixtures/artefakte/raummodell.*`). ## Meilensteine | M | Inhalt | Status | |---|---|---| | **M0** Repo-Setup | Monorepo-Gerüst, Pins, Setup-Scripts, CI, CLAUDE.md/README | 🟢 fertig | | **M1** Verträge & Regel-Kern | JSON-Schemas (7 Verträge) + Codegen + Regel-Interpreter TS & Python + Paritätstest | 🟢 fertig | | **M2** Scan-Spike | Eval-Notebook, Messung R1–R3 (parallel, blockiert nichts) | 🟡 gestartet | | **M3** Durchstich BAD ⭐ | Sample-Bad → Baseline → Solver P1–P3 → Viewer → Report → Mengen/KV-PDF | 🟢 DoD erfüllt | | **M4** Auswertung voll + Kurator | LV, Bauzeitenplan, Offert-Paket, DXF · Kurator + Mini-Eval · Stil-UI | 🟢 DoD erfüllt | | **M5** Durchstich WOHNEN | Sample-Wohnzimmer → Regelsatz/Katalog/Bilder wohnen → Solver mit freier Boden-Platzierung → LV/Bauzeit/Dokumente | 🟢 fertig | | **M6** Durchstich KÜCHE | Formwahl + lineare Baugruppe + API + Frontend; Grossraum über Zone geplant | 🟢 DoD erfüllt | | **M7** Scan-Integration (+AR) | Adapter · Space-Deploy · scan-worker · Upload/`/scan` · Korrektur-Modus fertig; offen: R1-Gate auf Colab (Schritt 5) | 🟡 gestartet | ## Was konkret existiert - **M0:** pnpm-Workspace (Node 22 LTS, pnpm gepinnt) · `apps/web` (React+Vite+TS, r3f-Smoke-Szene) · `services/engines` (uv, Python 3.12, FastAPI-Skeleton mit `/health`) · `packages/shared` (Schemas/Fixtures/ TS-Quellcode) · `data/`-Ordnerstruktur mit READMEs · Setup-Scripts (`scripts/setup.ps1` für Windows = Bryans Dev-System, `setup.sh` für CI) · GitHub-Actions-CI (Lint → Typecheck → Tests → Schema-Check) · `LICENSES.md`. - **M1:** 9 JSON-Schemas in `packages/shared/schemas/` (7 Verträge laut Schema-Spezifikation + Projekt-Hülle + Taxonomie) · Codegen beidseitig (`pnpm codegen` → TS-Typen + pydantic-v2-Modelle, generiert & eingecheckt) · **Regel-Interpreter doppelt** (`packages/shared/src/rules/` ↔ `services/engines/src/fp_engines/rules/`, Semantik-Doku im rules-README) · Stammdaten `data/rules/basis.json` + `bad.json` (Norm-Regelsatz-v0, Richtwerte «zu-verifizieren») + `data/taxonomy/stilachsen.json` (8 Achsen, ADR-0006) · goldene Fixtures (Sample-Bad 3.0×2.4 m, Plan ok/verletzt) + **Paritätstest grün** in vitest UND pytest · `POST /validate` liefert den `constraintReport` über die API (inkl. Fehler-Envelope). - **M2 (gestartet 2026-06-11):** `notebooks/scan-spike/` mit Colab-Notebook `spike_eval.ipynb` (P0–P5-Skelett: Frames+Gate, Tiefe DA-V2-Small, Grounding DINO; P1 Layout / P4 Spiegel als TODO bis zum ersten GPU-Lauf) · `capture_check.py` (lokaler Vorabcheck) · **R1-Testvideos von Bryan** (Bad, normal + weitwinkel) in `testdata/r1/` · Vorabcheck-Report mit **Aufnahme-Guideline v1** in `reports/r1-vorabcheck.md` (Befund: Material zu unscharf/texturarm für die Kette → R1 nach Guideline wiederholen; Weitwinkel ~3× besser) · **Ground Truth R1 erfasst** (Bryan 2026-06-11): L-förmig, 1.8×1.0 m mit Versatz 0.3 m (Nische 0.7×0.8 m), 1.56 m² → `ground-truth/r1.json` + reales Fixture `packages/shared/fixtures/artefakte/raummodell.r1-wc.json` (6 Wände, erster nicht-rechteckiger Grundriss). Offen: Raumhöhe, Türbreite, Objektmasse, Neuaufnahme nach Guideline. - **M2 (2026-06-14, getesteter Metrik-Kern):** `notebooks/scan-spike/` `eval_metrics.py` – **GPU-freier, abhängigkeitsfreier** Mess-Kern (Wandlängen-Fehler Median/Max, Fläche %, Rechtwinkligkeit, Objekt-Recall) + `gate()` (Go/Anpassen/Pivot aus den Spike-Schwellen). **`test_eval_metrics.py` grün** (5 Tests gegen R1-Ground-Truth: perfekte Schätzung = 0 Fehler, 1.56 m²/ 5.6 m reproduziert, 5-cm-Störung erkannt, Eckenzahl-Mismatch gemeldet, Recall). Notebook nutzt jetzt diesen Kern und kennt die **Ecken-Antippen-Variante** (`corners/.json`, Wandgenauigkeit GPU-frei). Die schweren ML-Schritte (Depth Anything V2, Grounding DINO, SAM2) brauchen GPU+Modell-Download → laufen auf **Colab (T4)**, nicht in dieser Umgebung (kein GPU, HF 403). - **M3 (2026-06-11):** kompletter End-to-End-Klickpfad: Raum wählen (Sample-Bad ODER echtes R1-WC) → «Plan vorschlagen» (Baseline-Kurator + Feasibility-first-Solver P1–P3, `data/catalog/bad.json`) → 3D-Viewer (r3f: Hülle + Box-Platzhalter, Auswahl, Pfeiltasten/«r» verschieben/ rotieren, sperren) mit **Live-Norm-Ampel** (TS-Interpreter, dieselben Regel-JSONs) → «Variante würfeln» (Seed) → Auswertung (Mengen + KV mit Bandbreite + Next-Steps) → **KV-PDF** (ReportLab, CI-Look). **Solver-Invariante ⭐ als Property-Test grün** (12 Seeds × 2 Räume, 0 ❌; Plan validiert gegen Plan-Schema; Determinismus + Varianten getestet). API: /samples/rooms · /catalog/{rt} · /rules/{rt} · /solve · /validate · /evaluate · /export/kv-pdf (Fehler-Envelope, 422 NO_FEASIBLE_PLACEMENT). - **M4 (2026-06-11, DoD erfüllt):** - **Dokumente:** LV (deklarativer Positionskatalog `data/positions/bad.json`, Trigger→Template, rückverfolgbar via herkunft) · Bauzeitenplan (Sequenz-DAG `data/sequence/bad.json` mit Trocknungs-Wartekanten) · Offertanfrage-Paket je Gewerk (ohne Preise + Rückgabeblatt) · DXF- Grundriss (ezdxf) · alles als API-Exporte (+PDF) und getestet. - **Kurator-Port** (`fp_engines/kurator.py`): Vorfilter → Prompt (`data/prompts/kurator-rolle.md`) → Validierung → Repair → Fallback; Ports baseline | llm-api/llm-local (FP_KURATOR_URL). **Mini-Eval** `scripts/kurator_eval.py`; **Gate-Stand: Baseline aktiv** (kein LLM konfiguriert – mit FP_KURATOR_URL erneut messen). - **Stil-UI:** 8 Sample-Bilder (SVG-Platzhalter) + Bild-Katalog `data/images/bad.json` · Swipe-Overlay + Preset-Klick · Smart Spider (SVG-Radar, Achsen aus /taxonomy) · POST /style/profile · Klickpfad Stil → /curate → /solve im Viewer verdrahtet (Begründung wird gezeigt). - **Alle MVP-Dokumente generierbar (DoD):** KV · Mengen · LV · Bauzeitenplan · Offertanfrage-Paket · Gewerke-Übersicht · Einkaufsliste · 2D-Plan (PDF **und** DXF) · 3D-Export (glTF 2.0, handgebauter Writer, deckungsgleich mit dem Box-Viewer) · Next-Steps (im KV). Im Viewer als «📄 Dokumente…»-Menü. **Eval-Gate entschieden: Baseline aktiv** (LLM darf sie per FP_KURATOR_URL herausfordern). - **Nicht code-seitig (bei Bryan):** Mini-Eval mit echtem LLM messen + Mensch-Rating; echte Bad-Fotos taggen (ersetzt SVG-Platzhalter). - **M5 (2026-06-12, Durchstich WOHNEN):** - **Sample-Raum:** `raummodell.wohnen-sample` (Wohnzimmer 4.5×3.6 m, 16.2 m², 4 massive Wände, Tür 0.9 m an kurzer Wand, Fenster als Öffnung, KEINE Fixpunkte = Trockenraum). In `/samples/rooms` automatisch eingehängt. - **Stammdaten wohnen:** `data/rules/wohnen.json` (host-binding Regal/ Wandbild, soft object-distance Sofa↔Couchtisch, hard clearance Esstisch ≥1.0 m + Sideboard ≥0.6 m; circulation bleibt Basis-Stub) · `data/catalog/wohnen.json` (28 Items: sofa/esstisch/tvmoebel P1, couchtisch/regal/sideboard/stehleuchte P2, teppich/pflanze/wandbild/ beistelltisch P3 – je 2–3 Stil-Varianten, Präfix `bbbbbbbb-…`) · `data/images/wohnen.json` + 8 SVG-Platzhalter (2 Presets) · `data/positions/wohnen.json` (Trockenraum-Gewerke: Maler Wände+Decke, Bodenleger, Elektro Leuchten-Anschluss, Möbel-/Wandmontage) · `data/sequence/wohnen.json` (Ausräumen → Maler[Trocknung] → Boden → Elektro → Montage → Endreinigung). - **Solver – freie Boden-Platzierung:** neu `_floor_candidates` (0.25-m- Raster über der Bodenpolygon-BBox, Yaw 0/90/180/270, nur mount=boden) + `_candidates` (Wand- ⊕ Boden-Kandidaten). Vorfilter/Caps unverändert (P2 300 / P3 200), neu Cap P1-Backtracking 400. Determinismus gewahrt (Kandidatenreihenfolge deterministisch vor dem seed-shuffle). Solve wohnen ~0.05–0.11 s, Testsuite 109 grün in ~12 s. - **Beweis freie Platzierung:** Property-Test + dedizierter Test (Couchtisch steht frei vor dem Sofa, Wandabstand >0.3 m). Alle Dokument-Exporte (LV/Bauzeit/KV/DXF/glTF/Plan-PDF) laufen für wohnen E2E. - **M6 Phase A (2026-06-12, Durchstich KÜCHE – Stammdaten/Zonen):** - **Sample-Räume:** `raummodell.kueche-sample` (geschlossene Küche 3.2×2.6 m, 4 massive Wände, Tür 0.9 m, Fenster, Fixpunkte wasser/abwasser/starkstrom/ elektro/lueftung an der Anschluss-Längswand) · `raummodell.grossraum-sample` (eine Hülle 7.0×4.5 m, `zones[]` = Küche 2.8×4.5 + Wohnen; Zonengrenze als `wall.kind:"offen"` mitten im Raum; Fixpunkte mit `zone`-Referenz an der massiven Stirnwand der Küchenzone). Beide automatisch in `/samples/rooms`. - **Stammdaten küche:** `data/rules/kueche.json` (connection spüle→wasser/ abwasser, kochfeld→starkstrom, GS→wasser/abwasser, kühlschrank→elektro je hard, dunstabzug→lueftung soft · object-distance kochfeld↔spüle 0.3 hard · clearance ≥1.0 m je Haupttyp · host-binding Hängeschrank/Dunstabzug an Wand) · `data/catalog/kueche.json` (37 Items, `normProfileVariante` ch55/eu60 für Korpusse+Geräte, Präfix `cccccccc-…`) · `data/images/kueche.json` + 8 SVG (2 Presets) · `data/positions/kueche.json` (Gewerke Küchenbauer/Sanitär/ Elektro/Lüftung/Maler/Bodenleger) · `data/sequence/kueche.json` (Demontage → Roh-Installation → Maler[Trocknung] → Boden → Küchenmontage → Fein-Installation → Endreinigung). - **Zonen-Ableitung:** `services/engines/src/fp_engines/zonen.py` – `zone_room(room, zone_id)` projiziert eine Zone auf ein eigenständiges, schema-valides Teilraum-Raummodell (floor=Zonen-Polygon via Shoelace, Wände auf die Zonenkanten geclippt: deckende Hüllenwand erbt kind massiv/offen, sonst synthetisch `virtuell`; Öffnungen + Offset umgehängt; nur Fixpunkte der Zone). Solver/Interpreter laufen unverändert darauf (nutzen nur `kind=="massiv"`). v0-Annahme: achsparallele Rechteck-Zonen. - **Tests:** `test_zonen.py` (Teilraum schema-valide, floor=Zonen-Polygon, nur Küchen-Fixpunkte, virtuelle Kante bei fehlender Hüllenwand, offene Zonengrenze nicht montierbar, massive Stirnwand erhalten, reine Funktion) · Schema-Check + beide Schema-Tests (TS+Py) um die 3 neuen Raum-Fixtures und die Regelsätze wohnen/kueche erweitert. Alles grün (Python 122, vitest 19). - **M6 Phase B (2026-06-12, Durchstich KÜCHE – Formwahl + Baugruppe):** - **Küchen-Solver** `services/engines/src/fp_engines/kueche.py`: - `formwahl(room, style_profile, norm_profile)` – Wandzüge (massiv, achsparallel) + Anschlusswand (Wasser/Abwasser-Fixpunkte am nächsten); Kandidaten-Formen nach Tabelle 1a hart gefiltert (I ≥2.4 m + Querbreite ≥1.6; Galley 2 parallel + Breite ≥2.4; L ≥1.8+≥1.2; U 3 Züge + Innenbreite ≥1.2; Insel Breite ≥3.4 UND Boden-Fixpunkt); Soft-Score 0.35·stil+0.30·ergo+0.20·arbeitsplatte+0.15·stauraum mit Stil→Form- Heuristik (`_FORM_STILZIEL` auf den Stilachsen). Rückgabe Top-3 DISTINKTE Formen (Begründung, Score, anchorWallIds, Nutzlänge). - `solve_kueche(...)` – lineare Baugruppe: Raster ch55/eu60, Slots ab Wandzug-Anfang (türfreies Teilstück), AMK-Füllung **P1** Spüle@Wasser → GS direkt daneben → Kochfeld ≥1 Slot Abstand + nahe Starkstrom → Kühl an Zeilenende → Dunstabzug als Wand-Placement über dem Kochfeld; **P2** Hochschrank an Enden, Unterschränke in Restslots, Hängeschrank-Ebene (nicht über Kochfeld, nicht vor Fenster), Füllstücke (0.05/0.15) ans Zeilenende; **P3** 1–2 Deko über `_floor_candidates`. Jede Platzierung gegen `_zulaessig` (evaluate_rules) – Solver-Invariante **verletzt==0** gewahrt, sonst `NoFeasiblePlacement`. `plan.assemblies` = eine kuechenzeile-UUID je Zeile, `meta.normProfile`. Stil-Auswahl reuse über `kurator._cos`. Regel-Interpreter **nicht angefasst** (Paritäts-Gesetz). Solve-Zeiten: geschlossene Küche ~0.09–0.12 s, Grossraum-Zone ~0.13 s (< 2 s). - **API:** `POST /kueche/formen` (Top-3) · `/solve` erweitert um `normProfile`/`form`/`zoneId`, Routing über `_effektiver_raum` (zoneId ODER Auto-Küchenzone im Grossraum → `zone_room`), Küchen-Pfad → `solve_kueche`; Response liefert zusätzlich `room` (effektiv geplanter Teilraum) für Viewer + Live-Ampel. Alle `/export/*` laufen für Küchen-Pläne (placements-basiert). - **Frontend:** Küchen-Erkennung (roomType kueche ODER Küchen-Zone); Normprofil-Toggle CH/EU, Formwahl-Karten (Name, Begründung, Score-Balken), Grossraum-Hinweis «geplant wird die Zone Küche»; Viewer + Ampel nutzen den von `/solve` zurückgegebenen effektiven Raum; «Variante würfeln» behält Form + Normprofil. Bad/Wohnen-Flows unverändert. - **Tests:** `test_kueche.py` (Formwahl: Insel ausgeschlossen, Anschlusswand in jeder Top-Form, Stil verschiebt Ranking · Baugruppe Property-Test {kueche, grossraum+zone}×{ch,eu}×6 Seeds = 0 ❌ + schema-valide + deterministisch · GS neben Spüle · Kochfeld↔Spüle ≥0.6 m · eine Assembly-ID je Zeile · Rasterpositionen · ch55/eu60-Varianten · Grossraum-Placements in Zone · Seed-Variation) + Endpoint-Tests (/kueche/formen, /solve kueche, /solve Grossraum-Zone, 1 Export-Smoke). **Gesamt: Python 163 grün (~9 s), vitest 19 grün, mypy/ruff sauber, Schema-Check 11 Files.** - **M3-Politur (2026-06-13, Verkehrsweg-Freiraumanalyse `circulation`):** Der seit M3 offene `circulation`-Stub ist jetzt ein **echter Evaluator** in BEIDEN Interpretern (1:1, ganzzahlige Raster/Erosion-Analyse: Bodenraster → freie Zellen → Manhattan-Distanztransform → Bottleneck via Union-Find über fallende Clearance-Schwellen; Anker = Türmünder, bei 1 Tür zusätzlich der offenste Punkt). **Marge = 2·Bottleneck − minWidth** (0.05-m-Raster). Die Live-Ampel und alle Reports zeigen jetzt ein Verkehrsweg-Urteil. - **Bewusst SOFT in v0** (Norm-Regelsatz lässt hard/soft offen): informiert, ohne die Solver-Invariante zu berühren. Der Solver ist noch nicht verkehrsweg-optimierend → seine Pläne zeigen oft eine **soft** circulation-Verletzung (bad knapp, wohnen/kueche verletzt) – ehrlich. - **Performance:** circulation läuft NICHT im Solver-Hot-Path – `_zulaessig` wertet dort per `nur_hart=True` nur harte Regeln (verhaltensneutral, da soft nie in `hard.summary` zählt); voller Report inkl. circulation nur 1×. - **Parität:** neuer goldener Fall `flur-circulation-verletzt` (Trennwand pincht Korridor) lockt den Verletzungs-Pfad TS↔Python; +4 Unit-Tests. - **Gesamt grün:** Python 171 (~20 s, 0.05-Raster), vitest 23, Parität beidseitig, mypy/ruff/Prettier/Schema sauber. - **M3-Politur (2026-06-13, 2D-Grundriss-Ansicht):** Neuer **Aufriss von oben** als SVG (`apps/web/src/Viewer2D.tsx`) – Header-Umschalter 3D↔2D, **2D ist Default** (normgerecht beurteilbar). Wände (massiv/offen), Öffnungen mit **Türschwenk**, Footprints **nach der Norm-Ampel eingefärbt** (verletzt/knapp/ ok/gesperrt), wandmontierte Objekte gestrichelt+transparent, klick-auswählbar, Legende. Geometrie-Kern `plan2d.ts` nutzt **`footprint()` aus @fp/shared/rules** → exakt deckungsgleich mit Solver/Interpreter/3D-Viewer (kein zweites Rotations-Mathe). **Test-Infra für `apps/web` neu aufgesetzt** (vitest 4.1.8): `plan2d.test.ts` (Transform/Footprint/Normale) + `Viewer2D.test.tsx` (Render-Smoke via renderToStaticMarkup). apps/web 8 Tests grün, Build ok. - **Drag&Drop (2026-06-13):** Footprints im 2D-Grundriss per Maus/Touch ziehen (unverriegelte Items), Live-Ampel rechnet sofort mit. Pointer→Welt über die SVG-CTM + `toWorld` (Umkehrung von `toScreen`, Roundtrip-Test). Pfeiltasten/«r»/sperren bleiben. - **«austauschen» (2026-06-13):** Im Auswahl-Panel das Item gegen eine Katalog-Alternative tauschen (gleicher Funktionstyp + gleiche normProfileVariante → Küchenzeile bleibt konsistent); Pose bleibt, Ampel rechnet die neuen Masse sofort durch. - **Verkehrsweg-aware Solver, Schritt 1 (2026-06-14, Tür-Korridor-Freihaltung):** Optionale (P2/P3) Objekte werden NICHT mehr in einen Tür-Zugangsstreifen (Türbreite × 0.9 m ins Rauminnere) platziert – reiner Geometrie-Filter in `solver.py` (`_tuer_korridore`/`_blockiert_korridor`), billig, paritätsneutral (kein Interpreter/Goldens, kein Grid im Hot-Path), P1 unberührt. **Effekt: Bad von knapp/verletzt auf circulation ok (+60 cm)** – genau «Korridor in P2/P3 freihalten». Test `test_tuer_korridor_frei_von_optionalen`. Suite 172 grün. - **Verkehrsweg-aware Solver, Schritt 2 (2026-06-14, Metrik-Fix Tür-Anker):** Die in Schritt 1 gefundene «Metrik-Fragilität» war der **Tür-Anker am Türmund** (1.5 Zellen) – dadurch war faktisch die fixe **Türbreite** der Engpass statt des durch Möbel beeinflussbaren Innenraum-Korridors. **Fix (1:1 TS & Python): Anker ~minWidth/2 HINTER die Tür** (ins Korridor-Innere). Der grosse Refactor (Chamfer/Wände-als-Hindernis) war NICHT nötig. **Effekt (echter Evaluator): wohnen −70 → ok +10, kueche −70 → knapp 0, bad bleibt ok +60, flur (echte Sperre) bleibt verletzt −30.** Goldens neu (Parität bit-identisch), 172 pytest + 23 vitest grün. Die Metrik urteilt jetzt sinnvoll & verteidigbar. - **hard-Hochstufung jetzt plausibel, aber bewusst noch soft:** Solverpläne sind jetzt circulation ok/knapp (kein verletzt in der Praxis). Hürde für hard ist nicht mehr die Metrik, sondern die **Hot-Path-Performance** (circulation ist teuer ~0.4 s/Auswertung; als hard liefe sie je Kandidat). Der billige Tür-Korridor-Filter (Schritt 1) verhindert die häufigsten Verletzungen schon ohne Grid – ein finaler Hard-Check + Keep-out könnte reichen. Mit Bryan abstimmen, ob/wann hard. Details: Brain `Learning-Circulation-Metrik-Fragilitaet`. - **Küchen-Politur (2026-06-14, Arbeitsdreieck als echter Score):** Der bisher fix auf 0.0 gesetzte `softScore.ergonomie` wird für Küchenpläne mit dem **gemessenen AMK-Arbeitsdreieck** (Spüle–Kochfeld–Kühlschrank) befüllt: drei Seitenlängen + Summe → Trapez-Score [0,1] (Seite ideal 1.2–2.7 m, Summe 4–8 m = effizient). Berechnet **nach** der Platzierung (`arbeitsdreieck()` in `kueche.py`) – **ohne Regel-Interpreter, Schema oder Goldens zu berühren**: das Feld existierte bereits im Plan-Schema, der Solver post-prozessiert nur den fertigen `constraintReport` (Paritäts-Gesetz unberührt, Invariante bleibt 0 ❌). `/solve` liefert für Küchen zusätzlich ein `arbeitsdreieck`-Detail (Seiten/ Summe/Bewertung); der Viewer zeigt es als Panel (effizient/akzeptabel/beengt/ weitläufig). Ehrliche Werte (echter Evaluator): Sample-Küche 3.2×2.6 m = **beengt** (0.26, Summe 2.2 m), Grossraum-Zone = **akzeptabel** (0.67, Summe 3.3 m) – differenziert nach Raumgrösse. `formwahl` (Pre-Placement-Ranking) bleibt bewusst beim Proxy (das echte Dreieck ist vor der Platzierung unbekannt). Suite: 187 pytest + 23 vitest + 8 apps/web-vitest grün, mypy/ruff/Prettier/ Schema sauber. Brain: Learning-Arbeitsdreieck-Ergonomie-Score. - **M7 Schritt 1 (2026-07-05, Scan-Vertrags-Naht):** Neues Paket `fp_engines/scan/` – **SpatialLM-Layout-Parser** (`spatiallm.py`, z-up- Textformat Wall/Door/Window/Bbox) + **Adapter** (`adapter.py`: z-up→y-up **ohne Spiegelung** (x,y,z)→(x,z,−y), Normierung auf min=0, mm-Rundung, Wand-Ketten→Boden-Polygon mit 2-cm-Toleranz, Öffnungen mit hostWall/offset/ sill, Objekt-Boxen mit needsReview, **deterministische uuid5-IDs**, ehrlicher `AdapterFehler` bei offener Hülle) + **Scan-Bundle-Posen v0** (`poses.py`: kanonisches `poses.json` – Meter/Sekunden/Quaternion xyzw + gravity; App- Exporte werden am Rand dorthin konvertiert). **Kernbeweis:** handgebautes R1-WC-Layout (`fixtures/scan/r1-wc.layout.txt`) ergibt nach dem Adapter **exakt** das Ground-Truth-Polygon von `raummodell.r1-wc.json` (1.56 m², 6 Wände). 11 neue Tests (Schema+pydantic-valide, Determinismus, Segment- Reihenfolge egal, Posen-Validierung). **Suite 198 grün**, mypy/ruff/Schema ok. Kontext: Brain ADR-0012 (Scan-Kette fix: AR-App-Aufnahme → known-pose Fusion → SpatialLM), Code-Fahrplan in `M2-M7-Scan-Pipeline-Fahrplan`. **Offen im Adapter-Umfeld:** Yaw-Vorzeichen + Winkel/Scale-Konvention gegen echte SpatialLM-Ausgabe verifizieren (R1-Lauf, Schritt 5); Konverter App-Export→poses.json, sobald Voxelio-/ARCore-Exportformat fixiert ist. ## Nächste Schritte (für die nächste Session) 1. **Scan-Code-Fahrplan (Brain: `M2-M7-Scan-Pipeline-Fahrplan`, von Bryan freigegeben 2026-07):** ✅ Schritt 1 Adapter fertig (s.o.) · ✅ **Schritt 2 Space-Deploy-Gerüst (2026-07-05):** `fp_engines/space.py` (EIN Origin: `/api` → Engines, `/` → gebautes Frontend – exakt die Dev-Proxy-Semantik, kein CORS) + `Dockerfile` (multi-stage Node-Build → Python/uv, Port 7860, User 1000) + `.dockerignore` + `deploy-space.yml` (pusht bei jedem main-Push einen frischen Einzel-Commit OHNE notebooks/.git – die 50 MB R1-Videos überschreiten HFs 10-MB-Grenze – mit HF-Frontmatter-README in den Space `Bryan-HSLU/FP_POC`; Secret `HF_TOKEN` liegt in GitHub). 2 neue Tests (test_space.py), Suite 200 grün, Smoke lokal ok (API + index.html). **Erster echter Space-Build läuft mit diesem Push** – Ergebnis auf huggingface.co/spaces/Bryan-HSLU/FP_POC prüfen; Groq-Secrets (`FP_KURATOR_URL/_MODEL/_API_KEY`) später in den Space-Settings setzen. · ✅ **Schritt 3 scan-worker (2026-07-05):** neues Paket `services/scan-worker` (eigenes uv-Projekt, Base-Dep nur numpy; gradio als Extra `worker`; torch/spatiallm/open3d NIE als Deps – nur Colab, NC!). **CPU-getesteter Geometrie-Kern:** `kamera.py` (OpenCV-Pinhole-unproject, Quaternion→Pose) · `fusion.py` (Voxel-Zentroid-Downsampling, deterministisch) · `ausrichtung.py` (z-up aus gravity via Rodrigues + `boden_ransac`-Fallback, Boden→z=0) · `skalierung.py` (Wandhöhen-Fallback; Posen metrisch ⇒ Skala 1) · `ply.py` (binary PLY XYZ+RGB = SpatialLM-Contract) · `pipeline.py` (Keyframe-Sampling = Laufzeit-Hebel, TiefenProvider injizierbar) · `worker.py` (Gradio-App, GPU-Teile guarded) · `notebooks/colab_worker.ipynb` (Starter: PAT-Clone, EINE Setup-Zelle mit Drive-Wheel-Cache-TODO, share-URL → manuell als `FP_SCAN_WORKER_URL` in den Space = Zeiger v0). **Kamera-Konvention festgezurrt** (poses.py-Docstring: OpenCV-Pinhole, T_wc; ARKit-Konverter = R·diag(1,−1,−1)). CI erweitert (lint/typecheck/ test + sync). **Suite: 200 engines + 22 scan-worker = 222 grün.** · ✅ **Schritt 5 vorbereitet (2026-07-06):** `_lauf_spatiallm` **verdrahtet** – ruft das offizielle SpatialLM-`inference.py` als Subprozess (`-p scene.ply -o layout.txt -m manycore-research/SpatialLM1.1-Qwen-0.5B`), dessen `to_language_string()`-Ausgabe = unser `layout.txt`-Vertrag (robust gegen SpatialLM-API-Änderungen, kein Nachbau der Vorverarbeitung). Repo-Pfad + Modell über `FP_SPATIALLM_DIR`/`FP_SPATIALLM_MODELL`. Notebook-Setup-Zelle klont SpatialLM, installiert den 1.1-Stack (Poetry + `poe install-sonata`) mit **flash-attn-Wheel-Cache auf Drive**. **Korrektur:** SpatialLM 1.1 nutzt **Sonata + flash-attn** als Backbone, **nicht** TorchSparse (das war nur 1.0) → Wheel-Cache zielt jetzt auf flash-attn. **Offen (= der eigentliche R1-Gate, Schritt 5):** erster echter Colab-Lauf – torch/CUDA-Pins an die Session anpassen (SpatialLM will 2.4.1/CUDA 12.4), Genauigkeit gegen R1 messen, Intrinsics aus AR-Metadaten statt Schätzung. Braucht Bryans AR-App-Aufnahmen (R1/R2/R3). · ✅ **Schritt 4 Upload-UI + `/scan` (2026-07-05):** `POST /scan` (multipart) nimmt ein **vorberechnetes Scan-Bundle** (`layout.txt` einzeln ODER `.zip` mit `layout.txt`/`poses.json`) → `_bundle_dateien` entpackt → `parse_layout` + `layout_to_raummodell` → schema-valides Raummodell + `warnungen` (needsReview-Objekte, fehlende Anschlüsse). `poses.json` wird, falls vorhanden, validiert (Warnung statt Abbruch). Fehler-Envelope: SCAN_INVALID (roomType/Layout kaputt) · SCAN_NO_LAYOUT (nur Video → Live-Weg folgt Schritt 5). Frontend: Header-Bedienung «📷 Scan laden» + Raumtyp-Select → `api.scan` (FormData) → Raum wird in die Liste gehängt und über `raumWaehlen` in den bestehenden Klickpfad eingespeist. **v0 = Vorberechnen** (Demo-Regel); Live-Weiterleitung an `FP_SCAN_WORKER_URL` bleibt Schritt-5- TODO. 6 neue Endpoint-Tests + Live-Smoke (R1-Layout → 1.56 m², 6 Wände, 2 Öffnungen). Suite: **206 engines + 22 scan-worker grün**, Frontend lint/typecheck/build sauber. · ✅ **3D-Objekte statt Box-Platzhalter (2026-07-05, Bryan-Wunsch):** `moebel3d.tsx` – alle 31 Katalog-funktionsTypen als Primitiv-Kompositionen (2–7 Teile; WC/Lavabo/Dusche mit Glaswänden/Sofa/Kochfeld …), Fallback = alte Box. **bbox-Treue als Test-Invariante** (`passtInBbox` über 5 Grössen): kein Primitiv verlässt w×d×h, Gruppen-Transform + Klick 1:1 wie vorher → Norm-Ampel-Farben/Logik unangetastet. Hinweis: glTF-Export bleibt bewusst Boxen (Viewer detaillierter als Export – POC-ok). · ✅ **Manueller Raum-Editor (2026-07-05, Bryan-Wunsch, Brain: Raum-Editor-Manuell):** dritte Erstellungsvariante «✏️ Raum erstellen» – `raumbau.ts` (pure, getestet: Rechteck/L-Form-Polygone, Shoelace, Öffnungs- Validierung, R1-L-Form = 6 Wände/1.56 m²) + `RaumEditor.tsx` (SVG-Draufsicht, Werkzeuge Tür/Fenster/Anschluss per Wand-Klick, Pflicht-Anschluss-Hinweis je Raumtyp, nutzt `plan2d`-Transform) → schema-konformes Raummodell (source manuell, `geometryConfirmed true` → keine Unsicherheits-Marge), geht wie Scan/Sample durch `raumWaehlen` in den Klickpfad. apps/web: 30 Tests grün, tsc/eslint/prettier/build sauber. · ✅ **Corporate-Identity-Theme (2026-07-05, Brain: Corporate-Identity):** `theme.ts` mit den 5 CI-Tokens (Dunkelgrün #243D35 primär, Orange #D6914F CTA, Off-White #F0F7F2 Grund, Dunkelblau, Salbeigrau) · Header-Wortmarke FUTURE (weiss) / PLANNING (orange, gesperrt) + Claim «Meet. Match. Build.» · alle alten Marken-Hexwerte in App/Stil/RaumEditor/Viewer2D/Viewer3D auf Tokens · **Ampel-/Statusfarben bewusst unangetastet** (verletzt/knapp/ gesperrt + P2/P3; P1 war schon CI-Grün, jetzt via Token + Klarstellungs- Kommentar) · CI-Fonts NICHT eingebettet (Lizenz offen lt. Brain, Kommentar im Code) · index.html-Titel mit Claim. · ✅ **Wizard-UI + Pitch-Grafikstil + Logos + Möbel-Materialfarben (2026-07-06, Bryan-Vorgaben aus CD-Sheet/Flyer/App-Mockups – Brain: Corporate-Identity §Layout- & Grafiksprache):** - **Schritt-für-Schritt-Wizard** statt alles auf einem Screen: 1 Projekt (Sample-Karten/Scan/Raum erstellen) → 2 Stil → 3 Vorschlag (Küche: Formwahl) → 4 Anpassen (Viewer/Ampel/würfeln) → 5 Auswertung & Export. Auto-Weiter nach Raumwahl und Plan; Badge-Navigation; keine Funktion entfernt (Checkliste im Commit). - **Pitch-Grafikstil als Tokens:** harter Schlagschatten OHNE Blur (8px 8px 0) + weisser Innen-Schein auf allen Karten; orange Pill-Buttons; dünne Versal-Titel; heller Verlauf-Grund (dunkler Flyer-Look = Marketing). - **Echte Logos:** `public/FP-Logo-horizontal.png` im hellen Header + Signet als Favicon (aus Brain-assets). - **Möbel-Materialfarben:** MATERIAL_FARBE je funktionsTyp (Keramik/Holz/ Salbei/Edelstahl/Terracotta/Grün) – greift NUR bei Ampel-Status ok; verletzt/knapp/gesperrt behalten Statusfarben (Ampel dominiert). Dafür Viewer3D jetzt mit statusById → **Norm-Ampel auch in 3D sichtbar**. apps/web 32 Tests grün, alle Gates sauber. CI-Fonts weiter bewusst nicht eingebettet (Lizenz offen). Bryan liefert eigene Piktogramme nach → gleicher Stil (inner glow + harter Schatten). · ✅ **Schritt 6 Scan-Korrektur-Modus (2026-07-06, Brain: ADR-0003 / M2-M7-Scan-Pipeline-Fahrplan):** nutzergeführte Korrektur gescannter Räume (~8.5 cm Unsicherheit, kein LiDAR). Reine Geometrie `korrektur.ts` (getestet, DOM-frei): `ecken` (eindeutige Ecken via Endpunkt-Matching mit Epsilon – Wand-Array NICHT umlauf-sortiert), `verschiebeEcke` (angrenzende Wände wandern mit), `snappe` (Achsen-Snap auf Nachbar-x/z < 7 cm → rechtwinklig = das Anti-8.5-cm-Werkzeug, dann 5-cm-Raster), `kettePolygon` (Umlauf schliessen analog `_kette` in adapter.py, offene Hülle = Fehler-Objekt statt Wurf → UI blockt «Übernehmen»), `wendeAn` (Öffnungs-offset-Clamp, Fixpunkt behält relatives t auf der Wandachse, Fläche/Polygon neu, Objekte bestätigt→needsReview:false / entfernt raus, geometryConfirmed→true, IDs stabil, 2-Dezimal-Rundung). UI `ScanKorrektur.tsx` (Muster RaumEditor): SVG-Draufsicht, Ecken als ziehbare Griffe (Pointer = Maus **und** Touch/ Antippen, live-Snap + Längen), Werkzeuge Tür/Fenster/Anschluss per Wand-Klick, Scan-Objekte als gestrichelte rotierte bbox (needsReview orange), rechte Spalte mit Scan-Warnungen + fehlenden Pflicht-Anschlüssen + Öffnungs-/ Anschluss-/Objekt-Listen, «Übernehmen» gesperrt bei Öffnungs-Fehler oder offener Hülle. App.tsx: `scanLaden` öffnet jetzt das Korrektur-Modal (State `korrektur`) statt direkt zu wählen; «Übernehmen» → korrigierter Raum in den Klickpfad, «Abbrechen» → Scan unkorrigiert (geometryConfirmed bleibt false); «📐 Korrigieren»-Knopf im Schritt «Projekt» für gescannte Räume (`meta.captureMethod === "ar"`, defensiv). 16 neue Tests (korrektur.test.ts, inkl. Schema-Validierung des korrigierten Raums). apps/web **52 Tests grün**, tsc/eslint/prettier/build sauber. · ✅ **Bild-Katalog aus Bryans Metadaten + echte Fotos (2026-07-06):** `data/images/{bad,wohnen,kueche}.json` von je 8 Platzhaltern auf **je 30 real getaggte Bilder** (aus Bryans ZIP-Metadaten generiert; alle 8 Achsen + reiche `attributTags` + Palette + Lizenz; schema-sauber, ungültiger `roomType:`-Tag entfernt). **Die echten PNG-Fotos hat Bryan direkt auf `main` hochgeladen** (`data/images/{roomType}/…png`, 90 Dateien ~2 MB) – bildRef ↔ Datei matcht 1:1 (0 fehlend). `BildKachel` zeigt als Fallback die Farbpalette, falls ein Foto mal fehlt. Tests robust: Bildzahl-Assert dynamisch, Preset-Test skippt (Bryans Bilder alle istPreset=false). Bryans neue detailliertere `moebel3d`- Objekte via `clampTeil`-Sicherheitsnetz bbox-treu gehalten (Norm-Ampel). · ✅ **Möbel-3D-Varianten-Bibliothek (2026-07-06, Brain: Moebel-3D-Varianten-Bibliothek):** neues optionales Katalog-Feld `modell3d` (Schema additiv + Codegen TS/pydantic) → Viewer-Lookup `modell3d → funktionsTyp-Standard → Box` via `bausatzSchluessel()`. 5 erste Varianten-Bausätze (wc-wandhaengend, lavabo-aufsatz, lavabo-doppel, sofa-l, dusche-eck) + 7 Katalog-Items zugeordnet. **Stil entscheidet die Variante** (Kurator-Score über achsenTags – Beleg: Profil warm/natürlich → Wc Klassik + Naturstein-Aufsatzbecken; Profil kühl/modern → wandhängendes Design-WC + Eckdusche). Erweitern = Katalog-Item mit achsenTags (+ optional neuer Bausatz), 3-Schritte-Anleitung im moebel3d-Docstring. Suite: Web 36 + engines 205/1 skip + scan-worker 22 grün. · ✅ **3D-Viewer: Türen/Fenster + stilgetriebene Oberflächen (2026-07-07, Bryan-Wunsch):** Wände jetzt aus massiven Segment-Quadern statt einer Voll-Box → Türen/Fenster sind **echte Löcher** (Vollsegmente + Sturz + Brüstung, ohne CSG). `raum3d.ts` (rein, getestet: `wandSegmente`/`clipZone`/`oeffnungsPose`) + `raum3d.tsx` (`Tuer3D` mit ~20°-Türblatt/Griff, `Fenster3D` mit getöntem Glas + Sprosse, `WandMitOeffnungen`, prozedurale CanvasTexture für Boden/Wand). Boden- & Wandoptik werden aus dem Stilprofil abgeleitet (`oberflaechen.ts`, rein/ deterministisch, **kein Schema-Eingriff** – Konvention wie `MATERIAL_FARBE`): Bad→Fliesen+Wandsockel 1.2 m, Wohnen→Parkett, Küche→Stein/Fliesen je Materialität; Achsen temperatur/helligkeit/materialitaet/farbigkeit steuern die Farbe (HSL-Herleitung). Ohne Stilprofil bleibt die heutige neutrale Optik; Türen/Fenster erscheinen immer. Abweichung dokumentiert: Wand-Fliesenzone per Höhen-Clip (2 gestapelte Zonen) statt CSG-Zonenschnitt – robust & dependency- frei. Suite: **Web +21 (raum3d 12 · oberflaechen 9)**, Gates lint/typecheck/ test(73)/build grün. · ✅ **3D-Viewer v2: Ansichts-Presets, Orbit+Begehung, 3D-Drag/Rotate, Ebenen, waehlbare Oberflaechen (2026-07-07, Bryan):** `Viewer3D.tsx` generalüberholt. **Ansichts-Presets** (Perspektive/Draufsicht/Front/Seite) als Leiste über dem Canvas – Kamera-Posen aus der Raum-BBox skaliert (`presetKamera`), OrbitControls-Ziel = Raumzentrum, Draufsicht mit winzigem z-Versatz gegen die Gimbal-Entartung. **Zwei Begehungsmodi**: 🌀 Orbit (wie bisher) und 🚶 Begehung = First-Person (Augenhöhe 1.6 m, WASD/Pfeile gehen, Drag umsehen, Doppeltipp = Schritt vorwärts – Maus **und** Touch, ohne PointerLock; **keine** Wandkollision, POC-bewusst). **Direkte Objekt- Interaktion wie im 2D**: gewähltes Möbel per Drag auf der y=0-Ebene verschieben (Raycast → 5-cm-Raster, `verschiebeNach`-Constraints), Doppelklick = +90° (`rotiereNach`); Klick öffnet weiterhin die Elementkarte. Nur in Schritt 4 (`interaktiv`), Vorschau read-only. **Ebenen-Toggles** Ampel (Statusfarben an/aus) + Wände (ausblendbar für freie Sicht von aussen). **Oberflächen als wählbare Objekte**: Klick auf Boden/Wand (kein Möbel getroffen) wählt die Fläche → Elementkarte-Ersatz «Oberfläche» in `SchrittAnpassen.tsx` mit 3 vordefinierten Boden-/Wandvarianten je Raumtyp (`variantenFuer`); die Wahl überschreibt lokal die stilabgeleitete Spez (`wendeVariantenAn`, **rein visuell, kein Schema-/API-Eingriff**) und **bleibt beim «Variante würfeln» erhalten** (eigener State, `loesen` fasst ihn nicht an; nur Raumwechsel setzt zurück). Reine Logik in `viewer3d-logik.ts` (getestet: BBox, Preset-Posen, Begehungs-Schrittvektor, Blickrichtung). Im Begehungsmodus ruhen die Möbel-Pfeiltasten (`viewer3dModus` in App, Konflikt vermeiden). Bewusst weggelassen: Wandkollision (POC), 3D-Labels/Beschriftungs- Ebene (der 3D-Viewer trägt keine Text-Labels), PointerLock (Drag-Look ist robuster + mobiltauglich). Suite: **Web +19 (viewer3d-logik 13 · oberflaechen +6) → 120**, Gates lint/typecheck/test/build grün. · ✅ **Viewer-Feinschliff (2026-07-07, Bryan):** (1) **3D-Bearbeitungsmodus entfernt** – Möbel werden im 3D NICHT mehr verschoben/gedreht (Drag/Rotate + DragEbene raus); 3D = reine Ansicht/Begehung, Klick wählt weiterhin aus (Produktkarte) und Flächen/Oberflächen bleiben wählbar. Bearbeiten läuft nur noch im 2D-Grundriss. (2) **2D-Planlook bei Ampel-aus**: ist die Ampel-Ebene aus, zeichnen die Objektsymbole mit schwarzem Stift (`FARBE_PLAN`) und **weisser Schraffur-Füllung** (SVG-`` 45°), Boden wird reinweiss – Architektenplan-Optik; Ampel an = farbig wie bisher. (3) **Zurück zur Titelseite**: «Zurück» auf Schritt 1 führt zurück ins Startmenü (`setPhase("menue")`, Label «← Zur Startseite»); AppRahmen um `zurueckLabel` erweitert. Gates grün, 120 Tests. · ✅ **Space-Deploy entschlackt + HF-Build-Fix (2026-07-07, Bryan):** Der HF- Space bekam bei jedem Deploy ~233 MB Bilder (wahrscheinliche Build-Fehler- Ursache). Fotos `data/images` von **PNG 941×1672 (1.7–2.8 MB)** → **JPEG q82, max 1400px** (197 MB → 19 MB), `bildRef` in bad/kueche/wohnen.json auf `.jpg` (StaticFiles serviert weiter). 43 ungenutzte Piktogramm-Originale (35 MB, teils Umlaut-Dateinamen) entfernt – die App lädt nur die 25 ASCII-Registry-Kopien. Deploy-Bilder gesamt **233 → 37 MB**. Engines 205 + Web 120 Tests grün. · ✅ **2D Layer-System v2 + Wand-/Fenster-Look (2026-07-07, Bryan):** Die vier «Ebenen»-Checkboxen ersetzt durch ein **Layer-Panel** (`layer2d.ts`): 8 **Darstellungs-Layer** (Wände/Öffnungen/Objekte/Bounding-Boxen/**Platzbedarf**/ Beschriftung/Masse/Ampel, Defaults: Boxen/Platzbedarf/Masse aus) + **Objekt- Layer** (jedes Möbel einzeln ein-/ausblendbar, CAD-artig, `versteckteObjekte`). **Platzbedarf-Layer**: Clearance-Zonen aus den `type:"clearance"`-Regeln (`clearanceRect` in plan2d) als Bewegungsflächen vor den Objekten (halbtransp. + gestrichelt, Tooltip = `hinweis`). **Fenster jetzt IN der Wandstärke** (echte Aussparung via `wandLuecken`, Glas über volle Wandstärke) statt Linie obenauf; **Wand-Look nach Referenzbild** (helle graue Füllung `#d3d3d3` + dünne Kante). `rules` von App an Viewer2D durchgereicht; Planlook (Ampel aus) bleibt. +11 Tests (plan2d clearance/wandLuecken, layer2d) → **Web 131**, Gates grün. · ✅ **UI-Redesign Etappe A: Designsystem + App-Rahmen (2026-07-07, Bryan-Konzept):** Fundament, das die 5 Schritt-Ansichten (Etappe B) nur noch konsumieren. **Designsystem** – globales Stylesheet `apps/web/src/fp.css` (in `main.tsx` importiert): `:root`-Farbrollen als CSS-Variablen (`--fp-primary/-dark/-accent/-muted/-soft/-background/-text`), Typografie-Vars (`--fp-font-display/-body`, Schriftdateien BEWUSST nicht eingebettet – Lizenz offen), vereinheitlichter Kartenstil `.fp-card` (+ `.fp-card-flach` flach, `.fp-card-aktiv` oranger Rand) mit hartem Schlagschatten OHNE Blur (7px 7px 0) + Innenschein, `.fp-button` (Press/Hover-Animation + `:active` scale), `.fp-schritt-neu` (einmalige Einblendung), `.fp-piktogramm-puls`, Responsive-Helfer `.fp-two-column`/`.fp-drei-karten` (@media 820px) + reduce- motion. **Hintergrund reinweiss** (index.html body + `--fp-background`); Off-White nur für Sekundärflächen. `theme.ts` **v2** – Rückwärtskompatibel (THEME/karte/pill/titel/schlagschatten/innerGlow behalten, zeigen auf die neuen Werte) + neu `FP_VAR` (CSS-Var-Strings) & `CSS` (Klassennamen-Konstanten). **App-Rahmen** `AppRahmen.tsx` (Header Logo/Projektname/Menü-Platzhalter · Fortschrittsweg · Inhalt zentriert max-width 1400 · Footer Zurück/Weiter). **Fortschrittsweg** `Fortschrittsweg.tsx` (Kreis+Label+Linie, aktiv=orange / erledigt=grün+✓ / gesperrt=salbei, mobil scrollbar) auf getesteter Logik `fortschritt.ts` (`schrittZustand`, 6 neue Tests). **Piktogramm-System** `Piktogramm.tsx` – 25 Inline-SVGs + **Drop-in** (`.png>` → onError-Fallback aufs SVG; Ordner `public/piktogramme/` mit README). **Lade- zustände** `Ladezustand.tsx` (pulsierendes Piktogramm + 4 Presets stil/ vorschlag/scan/dokumente – Etappe B verdrahtet). App.tsx: nur Rahmen- Präsentation ersetzt (weisser Grund, alter Header/Stepper/Footer → AppRahmen), Schritt-Logik/State unangetastet. **Web 79 Tests grün**, lint/typecheck/build sauber. · ✅ **UI-Redesign Etappe B: die 5 Ansichten (2026-07-07, Bryan-Konzept):** Schritt-Inhalte aus App.tsx in eigene Komponenten gezogen – `SchrittProjekt.tsx` («WAS MÖCHTEST DU PLANEN?», 3 Raumtyp-Karten mit Piktogramm + Auswahlzustand, darunter die 3 Wege Beispiel/Scan/Manuell; Raumtyp-Vorwahl steuert Scan-Raumtyp + filtert Beispiele) · `SchrittStil.tsx` (bildzentriertes Bewerten inline statt Overlay, Fortschritt «n von m», Like/Dislike/Überspringen mit Piktogrammen, Stilprofil-Karte mit Kurzworten/Spider/Top-3-%-Balken via fp-two-column, «Dein Stilprofil wird genauer.» alle 5 Bewertungen) · `SchrittVorschlag.tsx` (Zustand A Auslöser inkl. Küche-Normprofil/Formwahl; Zustand B «DEIN RAUMVORSCHLAG IST BEREIT» mit 2D-Vorschau, Stilbegründung, Haupt- materialien, Variante/Seed, einfacher Normstatus; EINE orange Hauptaktion «Ansehen & anpassen») · `SchrittAnpassen.tsx` (fp-two-column: Viewer links, Elementkarte rechts mit Normstatus passt/knapp/anpassen als Text+Piktogramm, Tausch-Alternativen, Sperren; Hilfe-Karte ohne Auswahl) · `SchrittAuswertung.tsx` («DEIN PROJEKT»-Zusammenfassung mit Kosten-Spanne/ Gewerke-Zahl/Status + Dokument-Karten-Raster mit Piktogrammen, bestehende api.dokument-Downloads). Ladezustände (stil/vorschlag/scan/dokumente) verdrahtet. Logik/State/API unverändert in App.tsx. Hinweis: Etappe-B-Agent brach am Session-Limit ab – Splice-Reparatur (verwaistes , TS-Narrowing Stil-Bild, Prettier) durch die Hauptsession. **Web 79 Tests grün**, lint/typecheck/build sauber. · ✅ **Stil-Schritt v2: Live-Profil pro Swipe, Swipe-Animation, Konvergenz-Stopp, Bildhöhe fix (2026-07-07, Bryan):** Swipe-Bild auf `min(46vh,420px)` begrenzt (kein Scrollen) · Stil-Analyse steht jetzt IMMER neben dem Bild (fp-two-column immer aktiv; Neutralzustand «Bewerte Bilder, dein Profil entsteht live.») und aktualisiert LIVE nach jeder Bewertung (neuer Prop `onZwischenProfil` → `api.styleProfile`, nur Profil-State, kein Schrittwechsel/Ladezustand; Balken re-animieren via `fp-schritt-neu`) · Swipe-Animation (Like gleitet rechts + oranger Akzent, Dislike links + dunkel, ~220 ms, reduce-motion respektiert, Überspringen ohne Animation) · Konvergenz-Stopp: neues getestetes Modul `stilkonvergenz.ts` (`istStabil(verlauf, minBewertungen=8, delta=0.06, fenster=2)`) stoppt das Bewerten sobald das Profil stabil ist («Weiter mit diesem Profil» / «Weiter verfeinern»), statt alle 30 Bilder zu erzwingen. Kleine, dokumentierte Abweichung: finales `onProfil` springt nun automatisch zu Schritt 3 (bestätigtes Profil → «weiter»). **Web 86 Tests grün** (79 + 7 neue), lint/typecheck/build sauber. · ✅ **2D-Viewer v2: Wandstärke, Objektsymbole, Ebenen, Messen, Drag&Rotate, Produkt-Panel (2026-07-07, Bryan):** Der 2D-Grundriss zeigt jetzt echte **Objektsymbole** (Architekten-Draufsicht) statt nur beschrifteter Boxen – neues reines Modul `symbole2d.ts`: je funktionsTyp eine Signatur (WC Schüssel+Spülkasten, Lavabo-Becken, Dusche Quadrat+Diagonale+Abfluss, Badewanne, Sofa Sitz/Rücken/Armlehnen, Ess-/Couchtisch, Bett Kissen+Decke, Regal/Sideboard/TV Tiefenlinien, Kochfeld 4 Kreise, Spüle Becken+Armatur, Kühlschrank Türlinie, Leuchten Kreis+Strahlen, Pflanze, Teppich gestrichelt, Schränke, …) – **alle 31 Katalog-funktionsTypen abgedeckt** (Test), Fallback = bisherige Box. Die Symbole nutzen die vorhandene Footprint-Transformation (Position/Yaw/bbox über `rotateY`+`toScreen`, deckungsgleich mit `footprint()`) und die **Ampel-/Materialfarbe** als Strichfarbe. **Wände mit echter `thickness`** als gefüllte Polygone (`wallQuad`, Fallback 0.1 m); Türen mit Aufschlag-Viertelkreis + Fenster als Doppellinie bleiben. **Ebenen-Leiste** (Beschriftung/Boxen/Masse/Ampel, Default alles an ausser Boxen). **Messwerkzeug** «📏 Messen» (zwei Klicks → Linie + Distanz in m, Snap auf Wand-Ecken < 15 cm, Escape/Moduswechsel löscht). **Direkte Interaktion**: gewähltes Objekt per Pointer-Drag verschieben (5-cm-Raster, bestehende `verschiebeNach`-Constraints) + **Rotations-Griff** (Drag rastet 15°, Doppelklick +90° via neues `rotiereNach`); Live-Ampel läuft wie beim Keyboard weiter. **Detail-Panel (Schritt 4)** erweitert: funktionsTyp, Prioritätsklassen- Badge, Materialfarbe-Punkt + Masse; Tausch-Alternativen als kleine Produkt- karten (Name + Farbpunkt + Masse) statt Select. Schritt 3 (Vorschau) bleibt read-only (`interaktiv=false`): Symbole/Wandstärke automatisch, keine Editier-Interaktion. Neue reine, getestete Geometrie in `plan2d.ts` (`wallQuad`, `distanz`, `wandEcken`, `naechsteEcke`, `yawAusZeiger`, `rasten`). App.tsx nur minimal angefasst (Props + `rotiereNach`); Keyboard-Steuerung unangetastet. **Web 101 Tests grün** (86 + 15 neue), lint/typecheck/build sauber. Bewusst weggelassen: typübergreifender Tausch (wie schon dokumentiert), Symbole als Fill (bewusst nur Strichkontur = Architektenlook, Ampel bleibt als Tint-Füllung + Rahmen lesbar). **Als Nächstes:** Presets optional kuratieren · ⚠️ 90 PNG (~180 MB) blähen Repo + Space-Deploy – ggf. Bildgrösse reduzieren/auslagern · Schritt 5 E2E gegen R1 auf Colab (braucht AR-App-Aufnahmen) · Bryans Piktogramme. Alt-Punkte unverändert offen: circulation auf `hard` hochstufen (Hot-Path-Performance, mit Bryan abstimmen); Küche post-POC: mehr Slot-Breiten (30/45/90), triangle-aware Slot-Optimierung. 2. **M2 Scan-Spike weiterführen:** Metrik-Kern + Ecken-Antippen-Pfad stehen & sind getestet (2026-06-14). **Offen – Bryan:** R1-Restmasse (Raumhöhe, Türbreite, Objektmasse) + Neuaufnahme nach Guideline + R2/R3; dann `spike_eval.ipynb` einmal auf **Colab (T4)** laufen lassen (GPU-Schritte gehen hier nicht). **Offen – Code:** P1 automatischer Layout-Fit (GPU) + P4 Spiegel/Glas-Maskierung; danach Gate-Entscheid → Learning ins Brain. 3. Goldens bewusst aktualisieren: `uv run python scripts/update_goldens.py` (aus `services/engines/`), nur zusammen mit Interpreter-Änderung committen. ## Bewusste Abweichungen / Engineering-Entscheide | Entscheid | Grund | Wo dokumentiert | |---|---|---| | Eigener Code proprietär (kein LICENSE-File) | Entscheid Bryan 2026-06-11 | README, CLAUDE.md | | `circulation` v0 **soft** statt hard (Norm-Regelsatz nennt hard) | Norm-Regelsatz lässt unter «Offene Fragen» hard/soft explizit offen; soft informiert ohne die Solver-Invariante zu brechen (Solver ist noch nicht verkehrsweg-aware). Hochstufung auf hard = bewusster Folgeschritt mit Solver-Support | rules/basis.json, rules-README, Learning | | `circulation`-Marge grob/konservativ (0.05-m-Raster, bbox-Blocker, Türmund-Pessimismus) | v0-Freiraumanalyse: ganzzahlig (paritätssicher), Manhattan-Distanztransform, achsparallele Bounding-Box je bodenstehendem Objekt; Türbreite cappt den Bottleneck. Tuning (Euklid, feiner, türbewusst) post-POC | interpreter.py/.ts (Docstring), Learning | | Kollision prüft vertikale Überlappung (Höhenintervalle) | Spiegel ÜBER Lavabo ist keine Kollision – nötig für P2-Wandobjekte | Interpreter beidseitig, Learning im Brain | | Editor: Drag&Drop nur im 2D-Grundriss (3D bleibt Pfeiltasten) | 2D hat eine eindeutige Screen→Welt-Umkehrung (toWorld); 3D-Drag bräuchte Raycasting – später | Viewer2D, plan2d.ts | | «austauschen» bietet nur gleichen Funktionstyp + gleiche normProfileVariante | hält Plan/Küchenzeile konsistent; typübergreifender Tausch (z.B. Dusche→Badewanne) wäre eine eigene Re-Solve-Logik – post-POC | App.tsx (alternativen) | | `door-swing` v0 als Rechteck (Breite×Radius) statt Viertelkreis | identische, einfache Geometrie in TS & Python; konservative Näherung | Code-Kommentar + Fixtures | | M5: Bett-Regel «Zugang ≥1 Längsseite» nicht umgesetzt → Wohnzimmer-Fokus | Mit den aktuellen Regel-Typen (clearance/object-distance/host-binding) nicht ausdrückbar; Schlafen-Spezifika (Bett, beidseitiger Zugang) auf später verschoben statt Interpreter zu ändern (Paritäts-Gesetz) | STATUS, wohnen.json | | M5: Esstisch inkl. Stühlen als EIN Footprint | POC-Vereinfachung – Stuhl-Einzelplatzierung wäre eigene Solver-Stufe; Footprint deckt Tisch + ausgezogene Stühle ab, clearance-Regel zusätzlich ≥1.0 m | catalog/wohnen.json (Item-Beschreibung) | | M5: relationalRules-Distanzen wohnen als Zentrum-zu-Zentrum kalibriert (z.B. `near:sofa:1.3` statt 0.45) | Der Solver-Relationsfilter misst Zentrumsabstand; bei grossen Ankern (Sofa 2.1×0.95) wäre der ergonomische Kantenabstand 0.40–0.45 m als Zentrumswert kollidierend. Die ergonomische Norm bleibt als **soft** object-distance-Regel (Kantenmass 0.40 m) erhalten | catalog/wohnen.json, rules/wohnen.json | | M6-A: Grossraum-Sample mit echter `wall.kind:"offen"`-Kante mitten im Raum (statt nur Zonen-Polygone) | Code verträgt innenliegende Segmente problemlos (kein Hüllen-Closure-Validator; Solver/Interpreter filtern auf `kind=="massiv"`). Die offene Kante modelliert die reale offene Zonengrenze explizit; `zone_room` clippt sie auf die Zonenkante und erhält `kind:"offen"`. Die synthetische `virtuell`-Erzeugung greift nur, wenn KEINE Hüllenwand die Zonenkante deckt (separat getestet) | grossraum-sample, zonen.py, test_zonen.py | | M6-A: GS↔Spüle-«maxDist»-Regel NICHT als `object-distance` umgesetzt | Der Regel-Interpreter kennt bei `object-distance` nur `minDist` (kein maxDist). «Geschirrspüler direkt neben Spüle» ist Baugruppen-/Slot-Logik → kommt in Phase B; Interpreter wird (Paritäts-Gesetz) nicht erweitert. Nähe bleibt v0 über `relationalRules:["near:spuele:0.9"]` am Katalog-Item | rules/kueche.json, catalog/kueche.json, STATUS | | M6-A: clearance je Küchen-Haupttyp (spuele/kochfeld/geschirrspueler/unterschrank) statt einer Sammelregel | `appliesTo` matcht auf `funktionsTyp`; eine Regel pro Typ ist die mit den bestehenden Regel-Typen ausdrückbare Form des «Gang vor Zeile ≥ 1.0 m». Echte Zeilen-/Gang-Geometrie (circulation) kommt mit Phase B | rules/kueche.json | | M6-B: Ecke bei L/U → **Eckschrank** (Politur 2026-06-14, behebt den früheren Totraum) | grid×grid-Eckquadrat am Wand-Stoss wird aus den Schenkel-Slots reserviert, ein Eckschrank (Karussell, ch55/eu60) gesetzt – VOR den Füllstücken, damit sie ihm ausweichen. Additiv (nur wenn zulässig), Invariante bleibt 0 ❌. L = 1 Ecke, U = 2 | kueche.py (`_eck_zonen`/`_platziere_eckschraenke`), catalog/kueche.json, test_kueche.py | | M6-B: Fenster blockieren Unterschränke NICHT, nur die Hängeschrank-Ebene meidet Fenster | Brüstungsfenster (sill ≥ 0.9 m) erlauben darunter eine Arbeitsfläche/Unterschrank; nur Hängeschränke dürfen nicht vor ein Fenster. Türen blockieren dagegen die ganze Zeile (Nutzlänge = türfreies Teilstück) | kueche.py (`_fuelle_haengeschraenke`, `_freies_teilstueck_intervall`) | | M6-B: Hängeschrank-Heuristik = Ebene über allen Unterschränken ausser Kochfeld | Über dem Kochfeld hängt der Dunstabzug; sonst wird jede freie Wandposition mit Hängeschrank belegt (vertikal getrennt von den Korpussen → keine Kollision dank Höhenintervall-Prüfung des Interpreters). Einfach + deckt den Stauraum-Score | kueche.py (`_fuelle_haengeschraenke`) | | M6-B: GS-«direkt neben Spüle» als Slot-Nachbarschaft (nicht als Regel) | Der Interpreter kennt kein `maxDist` bei object-distance (M6-A-Entscheid); die Baugruppe setzt den GS deterministisch in den Nachbarslot der Spüle. Damit ist die Forderung konstruktiv erfüllt, ohne den Interpreter zu ändern | kueche.py (`_platziere_geschirrspueler`) | | M6-B (Review-Fix): P1-Geräte sind Pflicht – fehlt Spüle/GS/Kochfeld/Kühlschrank → 422 NO_FEASIBLE_PLACEMENT (nur Dunstabzug best-effort, lueftung-connection ist soft) | Vorher entstand still ein Rumpf-Plan ohne Kühlschrank (constraintReport 0 ❌, aber fachlich unvollständig) – ehrliches Scheitern statt stiller Degradation | kueche.py (solve_kueche) | | M6-B (Review-Fix): Solver-Vorfilter toleriert Berührung (Überlappung ≤ 0.05 cm = halbe Rundungsquante) | Exakt anliegende Korpusse (Küchenzeile!) erzeugen Float-Krümel-Überlappungen ~1e-16; der Vorfilter war strenger als das Interpreter-Urteil (0.1-cm-Rundung) und verwarf gültige Slots – deshalb fehlte der Kühlschrank | solver.py (_schnell_unzulaessig, _BERUEHRUNGS_EPS) | | M6-B: `formwahl`-Score nutzt für Ergonomie weiterhin einen Proxy (echtes Dreieck erst NACH der Platzierung) | Formwahl läuft VOR der Platzierung → das echte Arbeitsdreieck ist dort unbekannt; der Proxy (kompakte I-Zeile, L/U-Bonus) ist richtungsrichtig. Das **gemessene** Dreieck füllt seit 2026-06-14 `softScore.ergonomie` des fertigen Plans (AMK-Trapez-Score, server-berechnet) – kein Eingriff in Interpreter/Schema/Goldens | kueche.py (`formwahl` Proxy · `arbeitsdreieck` Messung) | | M6-B: /solve-Response um `room` (effektiver Raum) erweitert | Response ist kein Schema-Vertrag (Plan-Schema bleibt unberührt). Der Grossraum-Fall braucht den abgeleiteten Teilraum für Viewer + Live-Ampel; additiv am pydantic-Response-Modell | api.py (`/solve`), App.tsx (`planRoom`) | ## Offene Fragen an Bryan - AR-Vorschau (Stretch) bestätigen → `vault/50_Umsetzung/AR-Vorschau-Konzept.md`. - Erste Katalog-Quelle für echte Items/Preise (POC-Fallback: Sample-Daten). - Spike-Testräume R1–R3 aufnehmen (sobald Eval-Notebook steht).