feat(bisaya-course): expand exercises for shopping, neighborhood visits, conflict resolution, and free speaking
All checks were successful
Deploy to production / deploy (push) Successful in 2m57s

- Added new exercises in multiple-choice, gap-fill, and situational response formats for categories including 'Einkaufen vertiefen', 'Nachbarschaft & Besuche', 'Rollenspiel - Konflikt und Hilfe', and 'Freies Sprechen - Alltag ohne Stütze'.
- Each exercise includes detailed instructions, question data, answer data, and explanations to enhance the learning experience for Bisaya language learners.
- Focused on practical scenarios to improve conversational skills and vocabulary retention.
This commit is contained in:
Torsten Schulz (local)
2026-03-31 09:04:24 +02:00
parent 53f0745faf
commit 6b3aee458a
2 changed files with 706 additions and 0 deletions

View File

@@ -2136,6 +2136,534 @@ const BISAYA_EXERCISES = {
},
explanation: 'Die Abschlussprüfung bündelt Weg, Organisation und Hilfe in einer letzten Miniszene.'
})
],
'Einkaufen vertiefen': [
{
exerciseTypeId: 2,
title: 'Gesamtpreis erfragen',
instruction: 'Wähle die passendste Einkaufsfrage.',
questionData: {
type: 'multiple_choice',
question: 'Du hast mehrere Dinge ausgesucht. Wie fragst du nach dem Gesamtpreis?',
options: ['Pila ni tanan?', 'Kapoy na ka?', 'Asa ang bata?', 'Tabangan tika.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Pila ni tanan?" fragt nach dem Gesamtpreis.'
},
{
exerciseTypeId: 1,
title: 'Menge ergänzen',
instruction: 'Fülle die Lücke mit dem passenden Mengenwort.',
questionData: {
type: 'gap_fill',
text: 'Pwede tulo ka{gap}?',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['buok']
},
explanation: '"buok" wird für zählbare Einzelstücke verwendet.'
},
withTypeName('situational_response', {
title: 'Einkauf abschließen',
instruction: 'Reagiere passend beim Bezahlen.',
questionData: {
type: 'situational_response',
question: 'Frage nach dem Gesamtpreis und sage dann, dass du es nimmst.',
keywords: ['tanan', 'kuhaon']
},
answerData: {
modelAnswer: 'Pila ni tanan? Kuhaon na nako.',
keywords: ['tanan', 'kuhaon']
},
explanation: 'Das ist ein sehr alltagsnaher Miniabschluss beim Einkaufen.'
})
],
'Nachbarschaft & Besuche': [
{
exerciseTypeId: 2,
title: 'Bei Nachbarn vorbeischauen',
instruction: 'Wähle die passendste Aussage für einen Besuch bei Nachbarn.',
questionData: {
type: 'multiple_choice',
question: 'Wie sagst du: "Wir waren bei den Nachbarn"?',
options: ['Niadto mi sa silingan.', 'Adto ta sa doktor.', 'Magdula ta.', 'Naa koy assignment.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"silingan" bedeutet Nachbar oder Nachbarschaft.'
},
{
exerciseTypeId: 1,
title: 'Einladung in die Nachbarschaft',
instruction: 'Fülle die Lücke mit dem passenden Besuchswort.',
questionData: {
type: 'gap_fill',
text: '{gap} mo unya.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['Bisita']
},
explanation: '"Bisita mo unya." lädt zu einem späteren Besuch ein.'
},
withTypeName('situational_response', {
title: 'Nachbarschaftsbesuch ankündigen',
instruction: 'Reagiere in zwei kurzen Sätzen.',
questionData: {
type: 'situational_response',
question: 'Sag, dass ihr bei den Nachbarn wart und dass sie später zu Besuch kommen können.',
keywords: ['silingan', 'bisita']
},
answerData: {
modelAnswer: 'Niadto mi sa silingan. Bisita mo unya.',
keywords: ['silingan', 'bisita']
},
explanation: 'Das verbindet Begegnung und Einladung in einem natürlichen Nachbarschaftskontext.'
})
],
'Rollenspiel - Konflikt und Hilfe': [
{
exerciseTypeId: 2,
title: 'Konflikt ruhig eröffnen',
instruction: 'Wähle die höflichste Eröffnung für ein schwieriges Gespräch.',
questionData: {
type: 'multiple_choice',
question: 'Wie leitest du ein Konfliktgespräch ruhig ein?',
options: ['Pwede nato istoryahan?', 'Pila ni tanan?', 'Sulod lang.', 'Nikaon na ka?']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Pwede nato istoryahan?" ist weich und gesprächsorientiert.'
},
{
exerciseTypeId: 1,
title: 'Hilfe ergänzen',
instruction: 'Fülle die Lücke mit der passenden Hilfeformel.',
questionData: {
type: 'gap_fill',
text: '{gap} tika.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['Tabangan']
},
explanation: '"Tabangan tika." bedeutet "Ich helfe dir."'
},
withTypeName('situational_response', {
title: 'Konflikt und Hilfe verbinden',
instruction: 'Reagiere kurz, höflich und lösungsorientiert.',
questionData: {
type: 'situational_response',
question: 'Bitte darum, das Problem zu besprechen, und biete anschließend Hilfe an.',
keywords: ['istoryahan', 'tabangan']
},
answerData: {
modelAnswer: 'Pwede nato istoryahan? Tabangan tika.',
keywords: ['istoryahan', 'tabangan']
},
explanation: 'Das Rollenspiel verbindet Deeskalation und konkrete Hilfe.'
})
],
'Freies Sprechen - Alltag ohne Stütze': [
{
exerciseTypeId: 2,
title: 'Freies Sprechen strukturieren',
instruction: 'Wähle den Ausdruck, mit dem du eine freie Aussage natürlich einleitest.',
questionData: {
type: 'multiple_choice',
question: 'Welche Form passt gut als Einleitung für freies Sprechen?',
options: ['Sa tinuod...', 'Pila ang plite?', 'Asa ang sakayan?', 'Sulod lang.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Sa tinuod..." eignet sich gut, um frei in eine Aussage hineinzukommen.'
},
{
exerciseTypeId: 3,
title: 'Freie Aussage bauen',
instruction: 'Ordne die Wörter zu einer typischen Einleitung für eine freie Alltagsaussage.',
questionData: {
type: 'sentence_building',
question: 'Baue: "Meistens..."',
tokens: ['Kasagaran']
},
answerData: {
correct: ['Kasagaran...']
},
explanation: 'Diese Einleitungen helfen, im freien Sprechen in Gang zu kommen.'
},
{
title: 'Alltag frei erzählen',
instruction: 'Sprich ohne deutsche Stütze eine kurze freie Alltagsaussage.',
exerciseTypeId: 8,
questionData: {
type: 'speaking_from_memory',
question: 'Erzähle kurz frei über deinen Alltag und nutze mindestens zwei dieser Einleitungen: Sa tinuod, Kasagaran, Usahay, Apan.',
expectedText: 'Sa tinuod... Kasagaran... Usahay... Apan...',
keywords: ['tinuod', 'kasagaran', 'usahay', 'apan']
},
answerData: {
type: 'speaking_from_memory'
},
explanation: 'Hier geht es nicht mehr um perfekte Vorgabe, sondern um flüssige eigene Produktion.'
}
],
'Langzeitreview - Intensiv I': [
{
exerciseTypeId: 2,
title: 'Frühe Muster reaktivieren',
instruction: 'Wähle den Ausdruck, der sicher im Langzeitgedächtnis sitzen sollte.',
questionData: {
type: 'multiple_choice',
question: 'Welches frühe Fürsorgemuster musst du sofort wiedererkennen?',
options: ['Nikaon na ka?', 'Asa ang porma?', 'Naa moy appointment?', 'Mubayad ko.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: 'Das Langzeitreview holt sehr frühe Kernmuster bewusst wieder nach vorn.'
},
{
exerciseTypeId: 1,
title: 'Frühe Preisfrage ergänzen',
instruction: 'Fülle die Lücke mit dem passenden frühen Kernwort.',
questionData: {
type: 'gap_fill',
text: '{gap} ni?',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['Tagpila']
},
explanation: '"Tagpila ni?" gehört zu den wichtigsten frühen Alltagsfragen.'
},
withTypeName('situational_response', {
title: 'Frühe Routinen bündeln',
instruction: 'Reagiere mit zwei sehr frühen Kernmustern.',
questionData: {
type: 'situational_response',
question: 'Begrüße jemanden kurz und frage dann, ob die Person schon gegessen hat.',
keywords: ['kumusta', 'nikaon']
},
answerData: {
modelAnswer: 'Kumusta ka? Nikaon na ka?',
keywords: ['kumusta', 'nikaon']
},
explanation: 'Das reviewt ganz bewusst sehr frühe, sehr wichtige Sozialmuster.'
})
],
'Langzeitreview - Intensiv II': [
{
exerciseTypeId: 2,
title: 'Frühe und späte Themen mischen',
instruction: 'Wähle den Ausdruck, der zum Reaktivieren späterer Alltagsfelder gehört.',
questionData: {
type: 'multiple_choice',
question: 'Welcher Ausdruck gehört klar zu Schule, Gesundheit oder Erledigungen?',
options: ['resibo', 'Kumusta ka?', 'Palangga taka.', 'Sulod lang.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"resibo" steht hier für spätere Erledigungs- und Alltagsblöcke.'
},
{
exerciseTypeId: 1,
title: 'Gesundheit reaktivieren',
instruction: 'Fülle die Lücke mit dem passenden Wort aus dem Gesundheitsbereich.',
questionData: {
type: 'gap_fill',
text: 'Adto ta sa {gap}.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['doktor']
},
explanation: 'Auch späte Alltagsfelder sollen im Langzeitreview schnell wieder greifbar sein.'
},
withTypeName('situational_response', {
title: 'Späte Themen bündeln',
instruction: 'Reagiere mit zwei kurzen Sätzen.',
questionData: {
type: 'situational_response',
question: 'Sage, dass ihr zum Arzt geht, und erwähne danach ein Dokument oder einen Beleg.',
keywords: ['doktor', 'resibo']
},
answerData: {
modelAnswer: 'Adto ta sa doktor. Naa ko resibo.',
keywords: ['doktor', 'resibo']
},
explanation: 'Das Langzeitreview mischt bewusst entfernte Themenfelder in einer kurzen Reaktion.'
})
],
'Hilfe & Unterstützung': [
{
exerciseTypeId: 2,
title: 'Um Hilfe bitten',
instruction: 'Wähle die passendste Bitte um Unterstützung.',
questionData: {
type: 'multiple_choice',
question: 'Wie fragst du höflich, ob dir jemand helfen kann?',
options: ['Pwede ka motabang?', 'Asa ang sakayan?', 'Naa moy appointment?', 'Sulod lang.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Pwede ka motabang?" ist eine direkte, natürliche Hilfsbitte.'
},
{
exerciseTypeId: 1,
title: 'Hilfe anbieten ergänzen',
instruction: 'Fülle die Lücke mit der passenden Hilfsformel.',
questionData: {
type: 'gap_fill',
text: '{gap} tika.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['Tabangan']
},
explanation: '"Tabangan tika." bedeutet "Ich helfe dir."'
},
withTypeName('situational_response', {
title: 'Hilfe erfragen und anbieten',
instruction: 'Reagiere in zwei kurzen Sätzen.',
questionData: {
type: 'situational_response',
question: 'Bitte erst um Hilfe und bedanke dich danach kurz für die Unterstützung.',
keywords: ['tabang', 'salamat']
},
answerData: {
modelAnswer: 'Pwede ka motabang? Salamat sa tabang.',
keywords: ['tabang', 'salamat']
},
explanation: 'Die Lektion verbindet Bitte und soziale Reaktion zu einem natürlichen Miniablauf.'
})
],
'Höflich reagieren und ablehnen': [
{
exerciseTypeId: 2,
title: 'Sanft ablehnen',
instruction: 'Wähle die höflichste weiche Absage.',
questionData: {
type: 'multiple_choice',
question: 'Wie lehnst du etwas freundlich und nicht zu direkt ab?',
options: ['Dili lang sa karon.', 'Tabang!', 'Nikaon na ka?', 'Mubayad ko.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Dili lang sa karon." klingt deutlich weicher als ein hartes Nein.'
},
{
exerciseTypeId: 1,
title: 'Später statt jetzt',
instruction: 'Fülle die Lücke mit der passenden weichen Reaktion.',
questionData: {
type: 'gap_fill',
text: '{gap} na lang.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['Sunod']
},
explanation: '"Sunod na lang." verschiebt höflich auf später.'
},
withTypeName('situational_response', {
title: 'Höflich verschieben',
instruction: 'Reagiere freundlich und weich.',
questionData: {
type: 'situational_response',
question: 'Lehne eine Einladung höflich für heute ab und verschiebe sie auf später.',
keywords: ['dili', 'sunod']
},
answerData: {
modelAnswer: 'Dili lang sa karon. Sunod na lang.',
keywords: ['dili', 'sunod']
},
explanation: 'Das ist genau die weiche soziale Reaktionsform dieser Lektion.'
})
],
'Feste & Einladungen': [
{
exerciseTypeId: 2,
title: 'Zur Feier einladen',
instruction: 'Wähle die passende Einladungsformel.',
questionData: {
type: 'multiple_choice',
question: 'Wie sagst du: "Ich lade dich ein"?',
options: ['Giinvite tika.', 'Adto ta sa doktor.', 'Kapoy na ka?', 'Asa imong bag?']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Giinvite tika." ist eine alltagsnahe Einladungsform.'
},
{
exerciseTypeId: 1,
title: 'Zur Fiesta fragen',
instruction: 'Fülle die Lücke mit dem passenden Wort.',
questionData: {
type: 'gap_fill',
text: 'Moadto ka sa {gap}?',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['pista']
},
explanation: '"pista" steht hier für Feier oder Fiesta.'
},
withTypeName('situational_response', {
title: 'Einladung und Treffpunkt',
instruction: 'Reagiere in zwei kurzen Sätzen.',
questionData: {
type: 'situational_response',
question: 'Lade jemanden ein und sage, dass ihr euch dort trefft.',
keywords: ['invite', 'didto']
},
answerData: {
modelAnswer: 'Giinvite tika. Magkita ta didto.',
keywords: ['invite', 'didto']
},
explanation: 'Die Lektion verbindet Einladung und Verabredung in einer natürlichen Sozialszene.'
})
],
'Freies Erzählen - Mein Alltag': [
{
exerciseTypeId: 2,
title: 'Tagesablauf einleiten',
instruction: 'Wähle die passendste Einleitung für einen Tagesablauf.',
questionData: {
type: 'multiple_choice',
question: 'Welche Formulierung passt gut als Start in einen erzählten Tagesablauf?',
options: ['Sa buntag...', 'Pila ni tanan?', 'Asa ang porma?', 'Sulod lang.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Sa buntag..." eröffnet natürlich einen erzählten Tagesabschnitt.'
},
{
exerciseTypeId: 1,
title: 'Tagesabschnitt ergänzen',
instruction: 'Fülle die Lücke mit dem passenden Zeitabschnitt.',
questionData: {
type: 'gap_fill',
text: 'Sa {gap}...',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['hapon']
},
explanation: '"Sa hapon..." ist eine häufige Einleitung für den Nachmittag.'
},
{
exerciseTypeId: 8,
title: 'Eigenen Alltag erzählen',
instruction: 'Sprich frei über deinen Tagesablauf.',
questionData: {
type: 'speaking_from_memory',
question: 'Erzähle kurz, was du morgens, nachmittags und abends machst.',
expectedText: 'Sa buntag... Sa hapon... Sa gabii...',
keywords: ['buntag', 'hapon', 'gabii']
},
answerData: {
type: 'speaking_from_memory'
},
explanation: 'Hier steht die freie, zusammenhängende Produktion im Vordergrund.'
}
],
'Freies Erzählen - Familie, Sorgen, Pläne': [
{
exerciseTypeId: 2,
title: 'Sorge ausdrücken',
instruction: 'Wähle die Formulierung, mit der du eine leichte Sorge ausdrückst.',
questionData: {
type: 'multiple_choice',
question: 'Wie sagst du natürlich: "Ich bin etwas besorgt"?',
options: ['Naguol ko gamay.', 'Mubayad ko.', 'Sulod lang.', 'Tagpila ni?']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Naguol ko gamay." drückt eine leichte Sorge oder Niedergeschlagenheit aus.'
},
{
exerciseTypeId: 1,
title: 'Plan ergänzen',
instruction: 'Fülle die Lücke mit dem passenden Planungswort.',
questionData: {
type: 'gap_fill',
text: 'Aduna koy {gap} unya.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['plano']
},
explanation: '"plano" ist hier das Schlüsselwort für einen bevorstehenden Plan.'
},
{
exerciseTypeId: 8,
title: 'Familie, Sorge und Plan frei verbinden',
instruction: 'Sprich frei in mehreren kurzen Sätzen.',
questionData: {
type: 'speaking_from_memory',
question: 'Erzähle kurz von Familie, einer Sorge und einem Plan für später.',
expectedText: 'Naguol ko gamay. Pero okay ra. Aduna koy plano unya.',
keywords: ['naguol', 'okay', 'plano']
},
answerData: {
type: 'speaking_from_memory'
},
explanation: 'Diese Lektion trainiert freie Verbindung von Gefühl, Familie und Planung.'
}
],
'Kultur, Familie & Sprache langfristig': [
{
exerciseTypeId: 2,
title: 'Kulturellen Kernbegriff erkennen',
instruction: 'Wähle den Ausdruck, der stark mit respektvollem Umgang verbunden ist.',
questionData: {
type: 'multiple_choice',
question: 'Welcher Ausdruck gehört besonders zum kulturellen Schwerpunkt von Respekt und Rücksicht?',
options: ['respeto', 'plite', 'resibo', 'assignment']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"respeto" steht direkt für Respekt im sozialen Umgang.'
},
{
exerciseTypeId: 2,
title: 'Familienkultur und Sprache',
instruction: 'Wähle den Ausdruck, der besonders mit sozialem Miteinander verbunden ist.',
questionData: {
type: 'multiple_choice',
question: 'Welcher Begriff verweist besonders auf gemeinschaftliches Mitziehen und gutes Miteinander?',
options: ['pakikisama', 'doktor', 'ATM', 'sukli']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"pakikisama" ist ein zentraler kultureller Begriff für harmonisches Miteinander.'
},
{
exerciseTypeId: 8,
title: 'Kulturelle Schlüsselwörter laut festigen',
instruction: 'Sprich die kulturellen Schlüsselwörter laut und bewusst.',
questionData: {
type: 'speaking_from_memory',
question: 'Sprich die Wörter respeto, pakikisama, amping und palihug laut und deutlich.',
expectedText: 'respeto pakikisama amping palihug',
keywords: ['respeto', 'pakikisama', 'amping', 'palihug']
},
answerData: {
type: 'speaking_from_memory'
},
explanation: 'Die Schlusslektion verankert kulturelle Schlüsselwörter bewusst als Langzeitmarker.'
}
]
};

View File

@@ -0,0 +1,178 @@
# Falukant: Geplante Schwangerschaft & Geburt Daemon- und Datenbankkonzept
Dieses Dokument beschreibt das **vereinheitlichte Konzept** für Schwangerschaft, die der **externe Daemon** (`UserCharacterWorker`, C++) verarbeiten soll, sowie die **Datenbank** und die **Abgrenzung zur bestehenden Zufallslogik**. Ziel ist, dass gesetzte Termine (`pregnancy_due_at`) und der gewählte Vater (`pregnancy_father_character_id`) u.a. aus dem Admin-Tool **tatsächlich zu Geburten führen**, ohne dass nur die Node-API „Geburt erzwingen“ funktioniert.
---
## 1. Ist-Zustand (Problem)
### 1.1 Datenbank (bereits vorhanden)
Auf `falukant_data."character"` existieren (siehe `backend/sql/add_character_pregnancy.sql`):
| Spalte | Typ | Bedeutung |
|--------|-----|-----------|
| `pregnancy_due_at` | `TIMESTAMPTZ` NULL | Erwarteter Geburtstermin |
| `pregnancy_father_character_id` | `INTEGER` NULL, FK auf `character(id)` | Vater-Charakter |
Die **Node-Backend**-Logik (Admin, `falukantService`) **liest und schreibt** diese Felder.
### 1.2 Daemon aktuelles Verhalten
In `src/usercharacterworker.cpp` / `src/usercharacterworker.h`:
- `processPregnancies()` führt **`QUERY_GET_PREGNANCY_CANDIDATES`** aus.
- Diese Query:
- bezieht sich **nur** auf Ehen (`falukant_type.relationship.tr = 'married'`),
- nutzt **keine** `pregnancy_*`-Spalten,
- modelliert Geburten **per Zufallsprozess** (altersabhängige Wahrscheinlichkeit),
- setzt implizit **`character1_id` = Vater** und **`character2_id` = Mutter** (fest verdrahtet, ohne Geschlechts- oder Rollenprüfung).
**Folge:** Wer im Spiel oder per Admin als „schwanger“ mit Termin und Vater gespeichert wird, **wird vom Daemon nicht erkannt**. Geburten aus diesem Konzept passieren nur, wenn sie **manuell** per Node (z.B. Admin „Geburt erzwingen“) ausgelöst werden.
**Zusätzlich:** `QUERY_AUTOBATISM` setzt nur `child_relation.name_set` nach einigen Tagen hat **nichts** mit Schwangerschafts-Spalten zu tun.
---
## 2. Soll-Konzept (Zwei parallele Wege, eine klare Priorität)
### 2.1 Weg A **Geplante Schwangerschaft** (DB-gesteuert, neu für den Daemon)
**Auslöser:** `pregnancy_due_at` ist gesetzt und der Zeitpunkt ist **fällig**.
**Regeln (Vorschlag):**
1. **Eine** Mutter ist immer der Datensatz in `character`, auf dem `pregnancy_due_at` und die Schwangerschaft „liegen“.
2. **Vater:** `pregnancy_father_character_id`
- Wenn gesetzt: muss ein existierender Charakter sein, **nicht** `mother.id`.
- Wenn `NULL`: Policy festlegen (siehe Abschnitt 8).
3. **Fälligkeit:** z.B. `date_trunc('day', pregnancy_due_at AT TIME ZONE 'Europe/Berlin') <= current_date` (oder einheitlich **UTC** wichtig ist **eine** Definition im Team).
4. Nach erfolgreicher Geburt: **`pregnancy_due_at` und `pregnancy_father_character_id` auf NULL** setzen (Schwangerschaft beendet).
Dieser Weg entspricht dem, was Admin und Spieler erwarten, wenn ein **Termin** existiert.
### 2.2 Weg B **Legacy: Zufallsgeburten bei Ehe** (bestehende Query)
Die Query `QUERY_GET_PREGNANCY_CANDIDATES` modelliert **„natürliche“** Zufallsgeburten ohne `pregnancy_*`-Felder.
**Entscheidung für die Umsetzung:**
- **Option B1:** Weg B **abschalten** oder stark reduzieren, wenn Weg A das offizielle Modell ist.
- **Option B2:** Weg B **beibehalten** für Atmosphäre, aber **nur**, wenn **keine** aktive geplante Schwangerschaft auf den betroffenen Charakteren existiert (doppelte Geburten vermeiden).
- **Option B3:** Weg B nur noch **Simulation**, bis Spiel-Logik „Konzeption“ explizit setzt (größerer Umbau).
**Empfehlung:** Mindestens **B2** oder **B1**, damit keine zwei Geburten pro Tag für dieselbe Ehe aus unterschiedlichen Regeln entstehen.
---
## 3. Datenbank Erweiterungen (Vorschlag)
Die Minimalvariante kommt **ohne** neue Spalten aus (nur Daemon-Logik). Für Robustheit und spätere Features sind **optionale** Erweiterungen sinnvoll:
### 3.1 Pflicht (kein Schema-Zwang, aber empfohlen)
- **Index** für den Daemon-Tick, z.B.:
```sql
CREATE INDEX IF NOT EXISTS idx_character_pregnancy_due
ON falukant_data."character" (pregnancy_due_at)
WHERE pregnancy_due_at IS NOT NULL;
```
### 3.2 Optional Metadaten
| Spalte | Typ | Zweck |
|--------|-----|--------|
| `pregnancy_source` | `TEXT` oder `ENUM` | z.B. `admin`, `gameplay`, `npc` für Logging und Regeln |
| `pregnancy_conception_at` | `TIMESTAMPTZ` | optional, für Anzeige/Quests |
| `pregnancy_birth_context` | `TEXT` | `marriage` | `lover` für korrekten `child_relation.birth_context` ohne Heuristik |
Wenn `pregnancy_birth_context` **nicht** eingeführt wird, leitet der Daemon den Kontext aus der **Beziehung** zwischen Mutter und Vater ab (verheiratet → `marriage`, Liebhaber → `lover`, sonst Default `marriage` oder Policy).
### 3.3 `child_relation` Parität mit Node
Das Sequelize-Modell erwartet u.a. `father_name`, `mother_name`, `legitimacy`, `birth_context`, `public_known`. Die **Daemon-INSERT**-Query (`QUERY_INSERT_CHILD_RELATION`) muss mit der **realen DB** übereinstimmen (Pflichtfelder, Defaults). Falls nötig:
- INSERT um **Namen** (aus Vordefiniert-Tabellen) und **legitimacy / birth_context / public_known** erweitern,
- oder DB-Defaults / Trigger ergänzen,
- **gleiche Semantik** wie `adminForceFalukantBirth` in `adminService.js` anstreben.
---
## 4. Daemon Umsetzung (technisch)
### 4.1 Neue SQL-Query: „fällige geplante Geburten“
**Skizze (logisch):**
- SELECT Mutter-`character` `c` mit:
- `c.pregnancy_due_at IS NOT NULL`
- `c.pregnancy_due_at` fällig (siehe Zeitzone)
- JOIN Vater `c_father` auf `c.pregnancy_father_character_id = c_father.id` **wenn** Vater Pflicht ist
- Pro Zeile: `father_cid`, `mother_cid`, `region_id`, `title_of_nobility` (vom passenden Elternteil), `last_name`, `father_uid`, `mother_uid` analog zur bestehenden Schleife.
**Wichtig:** Vater/Mutter **nicht** aus `relationship.character1/2` ableiten, sondern aus **expliziten IDs** (`pregnancy_father_character_id` + Zeilen-ID der Mutter).
### 4.2 Ablauf in `processPregnancies()`
1. `QUERY_AUTOBATISM` wie bisher (optional, Reihenfolge beachten).
2. **Neu:** Transaktion oder feste Reihenfolge:
- Kandidaten für **geplante Geburt** laden
- **pro Kandidat:** Kind einfügen (`QUERY_INSERT_CHILD` oder gemeinsame Funktion), `child_relation` einfügen, **Schwangerschaft** auf der Mutter **leeren**
- Benachrichtigungen (`children_update`, `falukantUpdateStatus`) wie bei bestehender Schleife
3. **Legacy-Query** nur ausführen, wenn nach Absprache (Option B1B3).
**Idempotenz:** Pro Tick darf dieselbe schwangere Zeile **nicht** zweimal gebären. Am sichersten: **UPDATE … RETURNING** oder **DELETE** der Schwangerschaftsdaten in derselben Transaktion wie das Kind.
### 4.3 Geschlecht der Eltern
Optional: Plausibilitätsprüfung (`gender` Mutter/Vater) kann im ersten Schritt weggelassen werden, sollte aber langfristig mit dem Spielregelwerk übereinstimmen.
---
## 5. Backend (Node) Abstimmung
- **Zeitzonen:** `pregnancy_due_at` wird in JS als `Date` gesetzt; Daemon muss **dieselbe** Fälligkeitsdefinition nutzen wie die UI („heute“ = 0 Tage).
- **WebSocket:** Nach Daemon-Geburt dieselben Events wie bei Admin-Geburt, damit Clients die Familie aktualisieren.
---
## 6. Frontend
- Hinweis in der Familien-Ansicht: „Geburt erfolgt automatisch am Termin“ (wenn Daemon aktiv), sonst „nur nach Admin-Aktion“ je nach Rollout.
---
## 7. Test-Checkliste
- [ ] Admin: Schwangerschaft mit Termin **heute** und Vater nach Daemon-Lauf: Kind existiert, Schwangerschaft weg.
- [ ] Kein Vater (`NULL`) definiertes Verhalten (Fehler loggen / überspringen / NPC-Vater **Policy**).
- [ ] Doppel-Tick: keine doppelte Geburt.
- [ ] Legacy-Zufallsgeburt: keine Kollision mit aktivem `pregnancy_due_at` auf derselben Mutter.
- [ ] `child_relation` und `character`-Kind konsistent mit Node-Admin-Geburt.
---
## 8. Offene Policy-Fragen (bitte vor Implementierung festlegen)
1. **Vater `NULL`:** Geburt abbrechen, stillen Vater aus Beziehung erraten, oder festen Platzhalter?
2. **Legacy-Query:** komplett entfernen oder nur noch unter Bedingungen?
3. **Zeitzone** für „Termin ist heute“: Server-UTC, Europe/Berlin, oder Nutzer-TZ?
4. **Liebschaftsgeburten:** nur über `pregnancy_*` + `birth_context`/`pregnancy_birth_context`, nie über reine Ehe-Query?
---
## 9. Referenzdateien im Repo
| Bereich | Datei |
|---------|--------|
| Daemon | `src/usercharacterworker.cpp` (`processPregnancies`) |
| SQL-Strings | `src/usercharacterworker.h` (`QUERY_GET_PREGNANCY_CANDIDATES`, `QUERY_INSERT_CHILD`, `QUERY_INSERT_CHILD_RELATION`) |
| DB-Spalten Schwangerschaft | `backend/sql/add_character_pregnancy.sql` |
| Modell Character | `backend/models/falukant/data/character.js` |
| Admin / Geburt Node | `backend/services/adminService.js` (`adminForceFalukantPregnancy`, `adminForceFalukantBirth`) |
| Familie API | `backend/services/falukantService.js` (`_getCharacterPregnancyOptional`, `getFamily`) |
---
*Stand: technische Analyse des Repos; zur Abstimmung mit Game-Design und Deployment des Daemons.*