# Falukant: Dienerschaft – Daemon-, Technik- und Umsetzungs-Spezifikation Dieses Dokument bündelt die umsetzungsreife Spezifikation für das Dienerschaftssystem in einer Datei. Es ersetzt für die technische Umsetzung die sonst übliche Aufteilung in: - Daemon-Spec - Daemon-Handoff - technisches Konzept - Implementierungs-Backlog Die fachliche Grundidee bleibt in [FALUKANT_SERVANTS_CONCEPT.md](/mnt/share/torsten/Programs/YourPart3/docs/FALUKANT_SERVANTS_CONCEPT.md) beschrieben. Dieses Dokument hier ist die Arbeitsgrundlage für Implementierung und Daemon-Anbindung. ## 1. Zielbild Die Dienerschaft ist ein Haussystem mit vier Kernwerten: - `servantCount` - `servantQuality` - `servantPayLevel` - `householdOrder` Diese Werte wirken auf: - monatliche Kosten - Repräsentation und Ansehen - Komfort und Ordnung des Haushalts - Ehezufriedenheit und Haushaltsfrieden - Diskretion bei Liebschaften - spätere Untergrund-Aufdeckungen ## 2. Systemgrenzen In Scope der ersten Version: - Dienerschaft hängt an `user_house` - House-UI zeigt und verändert Dienerwerte - externer Daemon verarbeitet Daily- und Monthly-Effekte - Familie, Liebschaften und Untergrund nutzen die resultierenden Werte mit Nicht in Scope der ersten Version: - einzelne benannte Diener - eigene Dienerrollen wie Küchenpersonal, Wachen, Zofen - eigene Eventketten nur für Diener - finales Balancing ## 3. Datenmodell ### 3.1 Bereits vorhandene Hausfelder In `falukant_data.user_house`: - `servant_count integer not null default 0` - `servant_quality integer not null default 50` - `servant_pay_level varchar(20) not null default 'normal'` - `household_order integer not null default 55` ### 3.2 Wertebereiche - `servant_count`: `0..999` - `servant_quality`: `0..100` - `servant_pay_level`: `low | normal | high` - `household_order`: `0..100` ### 3.3 Abgeleitete Werte Diese Werte müssen nicht persistent gespeichert werden, sondern können im Backend oder Daemon berechnet werden: - `expectedServantsMin` - `expectedServantsMax` - `staffingState` - `orderState` - `monthlyServantCost` - `discretionModifier` - `servantReputationModifier` - `marriageComfortModifier` ## 4. Erwartungswert der Dienerschaft Die Sollgröße hängt von Haus und Stand ab. ### 4.1 Basis nach Hausposition `house.house_type.position` ist die grobe Hausklasse. Empfohlene erste Regel: | Hausposition | Basis Min | Basis Max | |-------------|-----------|-----------| | `<= 2` | 0 | 1 | | `3` | 1 | 2 | | `4` | 2 | 4 | | `5` | 3 | 6 | | `>= 6` | 4 | 8 | ### 4.2 Standesbonus Aus `character.noble_title.level`: ```text titleBonus = floor(level / 3), mindestens 0 expectedMin = baseMin + titleBonus expectedMax = baseMax + titleBonus ``` ### 4.3 Zustandsklassen ```text if servantCount < expectedMin => understaffed if servantCount > expectedMax => overstaffed sonst => fitting ``` ## 5. Daily-Regeln für den externen Daemon ## 5.1 Daily-Input Pro Falukant-User mit Haus braucht der Daemon: - `falukant_user.id` - `user.id` bzw. `user.hashed_id` für Benachrichtigung - `character.id` - `character.reputation` - `character.noble_title_id` und idealerweise `character.nobleTitle.level` - `user_house.house_type_id` - `house_type.position` - `house_type.cost` - `servant_count` - `servant_quality` - `servant_pay_level` - `household_order` - optional für Verknüpfungen: - `marriage_satisfaction` oder `relationship_state.marriage_satisfaction` - aktive Liebschaften mit `visibility`, `discretion`, `risk` ## 5.2 Daily-Hilfswerte ```text payShift(low) = -6 payShift(normal) = 0 payShift(high) = +6 missing = max(0, expectedMin - servantCount) excessive = max(0, servantCount - expectedMax) qualityPart = round((servantQuality - 50) * 0.35) payPart = payShift(servantPayLevel) fitPenalty = missing * 10 + excessive * 4 ``` ## 5.3 Daily-Zielwert für Haushaltsordnung ```text targetHouseholdOrder = clamp( 55 + qualityPart + payPart - fitPenalty, 0, 100 ) ``` ## 5.4 Daily-Drift der Haushaltsordnung Die Ordnung springt nicht hart, sondern driftet langsam: ```text newHouseholdOrder = oldHouseholdOrder if oldHouseholdOrder < targetHouseholdOrder: newHouseholdOrder += min(2, targetHouseholdOrder - oldHouseholdOrder) if oldHouseholdOrder > targetHouseholdOrder: newHouseholdOrder -= min(2, oldHouseholdOrder - targetHouseholdOrder) ``` Zusatzregel: - bei `servantPayLevel = low` und `servantCount < expectedMin` zusätzlich `-1` - bei `servantPayLevel = high` und `servantQuality >= 65` zusätzlich `+1` Danach clamp auf `0..100`. ## 5.5 Daily-Drift der Dienerqualität Die Qualität ändert sich langsam: ```text qualityDelta = 0 if servantPayLevel = low: qualityDelta -= 1 if servantPayLevel = high: qualityDelta += 1 if servantCount < expectedMin: qualityDelta -= 1 if servantCount > expectedMax + 2: qualityDelta -= 1 if householdOrder >= 80: qualityDelta += 1 if householdOrder <= 30: qualityDelta -= 1 ``` Danach: - auf `-2..+2` pro Tag begrenzen - `servantQuality = clamp(servantQuality + qualityDelta, 0, 100)` ## 5.6 Daily-Effekt auf Ansehen Der Daily-Rufeffekt ist klein, damit Monats- und Ereigniseffekte wichtiger bleiben. ```text reputationDelta = 0 if titleLevel >= 4 and servantCount < expectedMin: reputationDelta -= 0.15 * missing if titleLevel <= 1 and servantCount > expectedMax: reputationDelta -= 0.10 * excessive if householdOrder >= 85 and servantCount between expectedMin and expectedMax: reputationDelta += 0.05 if householdOrder <= 25: reputationDelta -= 0.20 ``` Rundung: - intern als Dezimalwert möglich - falls nur Ganzzahlen gespeichert werden, über Tagespuffer oder Rundungsregel aggregieren ## 5.7 Daily-Effekt auf Ehe / Haushalt Wenn ein Ehe-Zufriedenheitssystem vorhanden ist: ```text marriageDelta = 0 if householdOrder >= 75: marriageDelta += 0.10 if householdOrder <= 35: marriageDelta -= 0.15 if servantCount < expectedMin: marriageDelta -= 0.10 ``` Wenn noch kein eigener Wert gespeichert wird: - diese Regel für später vormerken - aktuell nur `householdTension` oder UI-Ableitungen beeinflussen ## 5.8 Daily-Effekt auf Liebschaften / Diskretion Der Daemon berechnet einen Diskretionsmodifikator: ```text discretionModifier = 0 if servantQuality >= 70 and servantPayLevel = high and servantCount <= expectedMax: discretionModifier -= 8 if servantPayLevel = low: discretionModifier += 6 if servantCount > expectedMax + 1: discretionModifier += 4 if householdOrder <= 35: discretionModifier += 5 ``` Bedeutung: - negativer Wert verbessert Geheimhaltung - positiver Wert erhöht Entdeckungsrisiko Anwendung: - bei aktiven Liebschaften auf Sichtbarkeit/Skandalchance - bei Untergrundaktivitäten als Erfolgsmodifikator ## 5.9 Daily-Notifications Daily sendet nicht für jede Teildrift ein eigenes Event. Wenn sich einer dieser Punkte relevant verändert: - `household_order` - `servant_quality` - `reputation` - Ehe-/Liebschaftsfolgen über Diener dann: - `falukantUpdateFamily` mit `reason: "daily"` - danach `falukantUpdateStatus` Es gibt keinen separaten `reason` für Dienerschaft. ## 6. Monthly-Regeln für den externen Daemon ## 6.1 Monthly-Input Wie Daily, zusätzlich: - aktuelles Geld `falukant_user.money` ## 6.2 Monatskosten ```text basePerServant = max(20, round((houseType.cost / 1000) + 40)) qualityFactor = 1 + ((servantQuality - 50) / 200) payFactor(low) = 0.8 payFactor(normal) = 1.0 payFactor(high) = 1.3 monthlyServantCost = servantCount * basePerServant * qualityFactor * payFactor ``` Auf 2 Nachkommastellen runden. ## 6.3 Abbuchung Wenn genügend Geld vorhanden: - Geld abziehen - Aktivität z. B. `servants_monthly` Wenn nicht genügend Geld vorhanden: - so viel wie möglich abziehen oder auf 0 fallen lassen, je nach vorhandener Gesamtlogik - Unterversorgung markieren Empfehlung für die erste Version: - vollständige Abbuchung nur wenn genug Geld da - sonst `underfunded = true` ## 6.4 Folgen von Unterversorgung Bei Unterversorgung im Monat: ```text servantQuality -= 4 householdOrder -= 6 ``` Zusätzlich: - wenn `titleLevel >= 4`: `reputation -= 1` - wenn aktive Liebschaften vorhanden: Diskretionsmalus für den Folgemonat ## 6.5 Monatsbonus bei gutem Haushalt Wenn gleichzeitig: - `servantCount` innerhalb Sollbereich - `servantQuality >= 70` - `householdOrder >= 80` - `servantPayLevel != low` dann: - `reputation += 1` für hohe Stände ab `titleLevel >= 3` - kleiner Ehe-/Komfortbonus, falls System vorhanden ## 6.6 Monthly-Notifications Nach Monatsverarbeitung: - `falukantUpdateFamily` mit `reason: "monthly"` - danach `falukantUpdateStatus` ## 7. Handoff an den externen Daemon ## 7.1 Der externe Daemon muss lesen Aus Backend/DB: - `falukant_data.user_house` - `falukant_type.house` - `falukant_data.falukant_user` - `falukant_data.character` - Titel/Stand - optional aktive Ehe-/Liebschaftsdaten ## 7.2 Der externe Daemon muss schreiben Mindestens: - `user_house.servant_quality` - `user_house.household_order` - `character.reputation` oder entsprechender Rufwert Optional, falls vorhanden: - `relationship_state.marriage_satisfaction` - Hilfs-/Logtabellen für Monatskosten und Unterversorgung ## 7.3 Der externe Daemon muss senden Bei relevanten Änderungen: - `falukantUpdateFamily` - `falukantUpdateStatus` `reason` nur: - `daily` - `monthly` Keine zusätzlichen Diener-Reason-Werte. ## 7.4 Idempotenz Der Daemon muss verhindern, dass Daily/Monthly doppelt auf denselben Tick laufen. Empfohlen: - eigene Tick-Marker außerhalb dieses Projekts - oder Zeitstempel in Worker-Logs ## 8. Backend-Aufgaben in diesem Projekt ## 8.1 Bereits erledigt - Hausfelder in `user_house` - Migration - Produktions-SQL - House-API mit Dienerwerten - UI in `HouseView` - direkte Spieleraktionen: - einstellen - entlassen - Bezahlungsstufe ändern ## 8.2 Noch sinnvolle Backend-Nacharbeiten - eigenes Money-Label für Monatskosten, z. B. `servants_monthly` - optional eigener Read-Endpunkt nur für Dienerschaft - optionale Validierungsgrenzen serverseitig weiter schärfen - später: Ableitung von `householdTension` stärker an Diener koppeln ## 9. UI-Anforderungen Die House-UI soll anzeigen: - aktuelle Dienerzahl - Sollbereich - Monatskosten - Qualität - Haushaltsordnung - Bezahlungsstufe - Besetzungsstatus - Ordnungsstatus Die UI soll direkt erlauben: - `+1` Diener - `-1` Diener - Pay-Level wechseln Die UI braucht keine Daemon-Sonderlogik außer normalen House-/Status-Refresh. ## 10. API-Schnittstellen Bereits vorgesehen: - `GET /api/falukant/houses` - `POST /api/falukant/houses/servants/hire` - `POST /api/falukant/houses/servants/dismiss` - `POST /api/falukant/houses/servants/pay-level` ### Beispiel-Response für `GET /houses` ```json { "roofCondition": 100, "wallCondition": 100, "floorCondition": 100, "windowCondition": 100, "servantCount": 3, "servantQuality": 58, "servantPayLevel": "normal", "householdOrder": 63, "houseType": { "id": 5, "position": 5, "cost": 273000, "labelTr": "family_house" }, "servantSummary": { "expectedMin": 3, "expectedMax": 6, "monthlyCost": 925.4, "staffingState": "fitting", "orderState": "stable" } } ``` ## 11. Technische Architektur ### 11.1 Quelle der Wahrheit Quelle der Wahrheit für: - Stammdaten und persistente Hauswerte: dieses Backend / Datenbank - Tick-Ausführung: externer Daemon ### 11.2 Verantwortungstrennung Dieses Projekt: - speichert Werte - bietet UI und API - berechnet einfache Hilfswerte für Anzeige Externer Daemon: - tägliche und monatliche Veränderung - Kostenabbuchung - Reputationseffekte - Verknüpfung mit Familie, Liebschaften und Untergrund ### 11.3 Warum so Damit: - Spiellogik nicht doppelt tickt - UI trotzdem schon benutzbar ist - der Daemon später nur auf stabile Felder aufsetzen muss ## 12. Implementierungs-Backlog ## B1 Datenbasis Status: erledigt Aufgaben: - Hausfelder in `user_house` - Migration - Produktions-SQL Done: - Felder vorhanden - Model aktualisiert ## B2 Haus-Service Status: erledigt Aufgaben: - Sollbereich berechnen - Monatskosten berechnen - Zustandslabels ableiten Done: - `servantSummary` wird im House-Read geliefert ## B3 Spieleraktionen Status: erledigt Aufgaben: - einstellen - entlassen - Bezahlung ändern Done: - Endpunkte vorhanden - UI verdrahtet ## B4 House-UI Status: erledigt Aufgaben: - Anzeige in `HouseView` - Aktionen - Locale-Texte Done: - HouseView zeigt den Dienerblock ## B5 Daemon Daily Status: offen Aufgaben: - `expectedMin/Max` im Worker berechnen - `householdOrder` driften - `servantQuality` driften - kleinen Reputationseffekt anwenden - Diskretionsmodifikator für Liebschaften ableiten - `daily`-Refresh senden Done-Kriterien: - täglicher Tick verändert Hauswerte nachvollziehbar - keine zusätzlichen UI-Reason-Werte nötig ## B6 Daemon Monthly Status: offen Aufgaben: - Monatskosten berechnen - Geld abbuchen - Unterversorgung behandeln - Monatsrufeffekte anwenden - `monthly`-Refresh senden Done-Kriterien: - Monatskosten und Unterversorgung sind im Spiel spürbar ## B7 Integration mit Familie / Liebschaften Status: offen Aufgaben: - `householdOrder` auf Ehekomfort mappen - Diskretionsmodifikator in Skandal-/Liebschaftslogik einbeziehen - schlechte Bezahlung oder Überbesetzung als Gerüchtefaktor nutzen Done-Kriterien: - Dienerschaft beeinflusst Familien- und Liebschaftssystem real ## B8 Integration mit Untergrund Status: offen Aufgaben: - `investigate_affair` nutzt Dienerwerte - schlechter Haushalt erhöht Aufdeckungschance - guter, diskreter Haushalt senkt Erfolgswahrscheinlichkeit Done-Kriterien: - Untergrund spürt Dienerschaft in Erfolgsmodifikatoren ## B9 Balancing Status: offen, bewusst spätere Phase Aufgaben: - Kosten, Rufwerte, Driftgeschwindigkeiten und Schwellwerte feinjustieren ## 13. Produktionshinweise Wenn keine Migrationen laufen: - [add_servants_to_user_house.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/add_servants_to_user_house.sql) ausführen Der externe Daemon muss erst danach aktiviert werden, damit die Felder sicher vorhanden sind. ## 14. Empfehlung für die nächste Reihenfolge Empfohlene Reihenfolge ab jetzt: 1. Produktions-SQL einspielen 2. B5 Daily im externen Daemon 3. B6 Monthly im externen Daemon 4. B7 Familie/Liebschaften anbinden 5. B8 Untergrund anbinden 6. B9 Balancing ## 15. Kurzfazit Die Haus- und UI-Basis ist bereits eingebaut. Für eine vollständige Spielwirkung fehlen jetzt vor allem die beiden externen Worker-Blöcke: - tägliche Drift - monatliche Kosten und Folgen Mit dieser Datei sollte der externe Daemon direkt implementierbar sein, ohne weitere Konzeptdokumente zu benötigen.