diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js
index a202285..226b4f3 100644
--- a/backend/controllers/falukantController.js
+++ b/backend/controllers/falukantController.js
@@ -146,6 +146,9 @@ class FalukantController {
this.getUserHouse = this._wrapWithUser((userId) => this.service.getUserHouse(userId));
this.getBuyableHouses = this._wrapWithUser((userId) => this.service.getBuyableHouses(userId));
this.buyUserHouse = this._wrapWithUser((userId, req) => this.service.buyUserHouse(userId, req.body.houseId), { successStatus: 201 });
+ this.hireServants = this._wrapWithUser((userId, req) => this.service.hireServants(userId, req.body?.amount), { successStatus: 201 });
+ this.dismissServants = this._wrapWithUser((userId, req) => this.service.dismissServants(userId, req.body?.amount));
+ this.setServantPayLevel = this._wrapWithUser((userId, req) => this.service.setServantPayLevel(userId, req.body?.payLevel));
this.getPartyTypes = this._wrapWithUser((userId) => this.service.getPartyTypes(userId));
this.createParty = this._wrapWithUser((userId, req) => {
diff --git a/backend/migrations/20260320002000-add-servants-to-user-house.cjs b/backend/migrations/20260320002000-add-servants-to-user-house.cjs
new file mode 100644
index 0000000..b83d1a8
--- /dev/null
+++ b/backend/migrations/20260320002000-add-servants-to-user-house.cjs
@@ -0,0 +1,53 @@
+'use strict';
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+ async up(queryInterface, Sequelize) {
+ await queryInterface.addColumn(
+ { schema: 'falukant_data', tableName: 'user_house' },
+ 'servant_count',
+ {
+ type: Sequelize.INTEGER,
+ allowNull: false,
+ defaultValue: 0
+ }
+ );
+
+ await queryInterface.addColumn(
+ { schema: 'falukant_data', tableName: 'user_house' },
+ 'servant_quality',
+ {
+ type: Sequelize.INTEGER,
+ allowNull: false,
+ defaultValue: 50
+ }
+ );
+
+ await queryInterface.addColumn(
+ { schema: 'falukant_data', tableName: 'user_house' },
+ 'servant_pay_level',
+ {
+ type: Sequelize.STRING(20),
+ allowNull: false,
+ defaultValue: 'normal'
+ }
+ );
+
+ await queryInterface.addColumn(
+ { schema: 'falukant_data', tableName: 'user_house' },
+ 'household_order',
+ {
+ type: Sequelize.INTEGER,
+ allowNull: false,
+ defaultValue: 55
+ }
+ );
+ },
+
+ async down(queryInterface) {
+ await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'household_order');
+ await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'servant_pay_level');
+ await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'servant_quality');
+ await queryInterface.removeColumn({ schema: 'falukant_data', tableName: 'user_house' }, 'servant_count');
+ }
+};
diff --git a/backend/models/falukant/data/user_house.js b/backend/models/falukant/data/user_house.js
index ad51590..8946243 100644
--- a/backend/models/falukant/data/user_house.js
+++ b/backend/models/falukant/data/user_house.js
@@ -24,6 +24,26 @@ UserHouse.init({
allowNull: false,
defaultValue: 100
},
+ servantCount: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 0
+ },
+ servantQuality: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 50
+ },
+ servantPayLevel: {
+ type: DataTypes.STRING(20),
+ allowNull: false,
+ defaultValue: 'normal'
+ },
+ householdOrder: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 55
+ },
houseTypeId: {
type: DataTypes.INTEGER,
allowNull: false
diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js
index 5af0455..10fd166 100644
--- a/backend/routers/falukantRouter.js
+++ b/backend/routers/falukantRouter.js
@@ -65,6 +65,9 @@ router.get('/houses/buyable', falukantController.getBuyableHouses);
router.get('/houses', falukantController.getUserHouse);
router.post('/houses/renovate-all', falukantController.renovateAll);
router.post('/houses/renovate', falukantController.renovate);
+router.post('/houses/servants/hire', falukantController.hireServants);
+router.post('/houses/servants/dismiss', falukantController.dismissServants);
+router.post('/houses/servants/pay-level', falukantController.setServantPayLevel);
router.post('/houses', falukantController.buyUserHouse);
router.get('/party/types', falukantController.getPartyTypes);
router.post('/party', falukantController.createParty);
diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js
index a2ddbf7..99f1dd9 100644
--- a/backend/services/falukantService.js
+++ b/backend/services/falukantService.js
@@ -4014,25 +4014,45 @@ class FalukantService extends BaseService {
async getUserHouse(hashedUserId) {
try {
- const user = await User.findOne({
- where: { hashedId: hashedUserId },
+ const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
+ const userHouse = await UserHouse.findOne({
+ where: { userId: falukantUser.id },
include: [{
- model: FalukantUser,
- as: 'falukantData',
- include: [{
- model: UserHouse,
- as: 'userHouse',
- include: [{
- model: HouseType,
- as: 'houseType',
- attributes: ['position', 'cost']
- }],
- attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition']
- }],
- }
+ model: HouseType,
+ as: 'houseType',
+ attributes: ['id', 'position', 'cost', 'labelTr']
+ }],
+ attributes: [
+ 'roofCondition',
+ 'wallCondition',
+ 'floorCondition',
+ 'windowCondition',
+ 'servantCount',
+ 'servantQuality',
+ 'servantPayLevel',
+ 'householdOrder',
+ 'houseTypeId'
]
});
- return user.falukantData[0].userHouse ?? { position: 0, roofCondition: 100, wallCondition: 100, floorCondition: 100, windowCondition: 100 };
+
+ if (!userHouse) {
+ return {
+ position: 0,
+ roofCondition: 100,
+ wallCondition: 100,
+ floorCondition: 100,
+ windowCondition: 100,
+ servantCount: 0,
+ servantQuality: 50,
+ servantPayLevel: 'normal',
+ householdOrder: 55,
+ servantSummary: this.buildServantSummary(null, falukantUser.character)
+ };
+ }
+
+ const plainHouse = userHouse.get({ plain: true });
+ plainHouse.servantSummary = this.buildServantSummary(plainHouse, falukantUser.character);
+ return plainHouse;
} catch (error) {
console.log(error);
return {};
@@ -4089,9 +4109,14 @@ class FalukantService extends BaseService {
if (oldHouse) {
await oldHouse.destroy();
}
+ const servantDefaults = this.getInitialServantState(house.houseType, falukantUser.character);
await UserHouse.create({
userId: falukantUser.id,
houseTypeId: house.houseTypeId,
+ servantCount: servantDefaults.servantCount,
+ servantQuality: servantDefaults.servantQuality,
+ servantPayLevel: servantDefaults.servantPayLevel,
+ householdOrder: servantDefaults.householdOrder
});
await house.destroy();
await updateFalukantUserMoney(falukantUser.id, -housePrice, "housebuy", falukantUser.id);
@@ -4109,6 +4134,232 @@ class FalukantService extends BaseService {
return (house.houseType.cost / 100 * houseQuality).toFixed(2);
}
+ getInitialServantState(houseType, character) {
+ const expected = this.getServantExpectation(houseType, character);
+ return {
+ servantCount: expected.min,
+ servantQuality: 50,
+ servantPayLevel: 'normal',
+ householdOrder: this.calculateHouseholdOrder({
+ servantCount: expected.min,
+ servantQuality: 50,
+ servantPayLevel: 'normal',
+ houseType,
+ character
+ })
+ };
+ }
+
+ getPayLevelMultiplier(payLevel) {
+ switch (payLevel) {
+ case 'low':
+ return 0.8;
+ case 'high':
+ return 1.3;
+ default:
+ return 1;
+ }
+ }
+
+ getPayLevelQualityShift(payLevel) {
+ switch (payLevel) {
+ case 'low':
+ return -6;
+ case 'high':
+ return 6;
+ default:
+ return 0;
+ }
+ }
+
+ getServantExpectation(houseType, character) {
+ const housePosition = Number(houseType?.position || 0);
+ const titleLevel = Number(character?.nobleTitle?.level || character?.titleOfNobility?.level || 0);
+
+ let min = 0;
+ let max = 1;
+
+ if (housePosition >= 6) {
+ min = 4;
+ max = 8;
+ } else if (housePosition === 5) {
+ min = 3;
+ max = 6;
+ } else if (housePosition === 4) {
+ min = 2;
+ max = 4;
+ } else if (housePosition === 3) {
+ min = 1;
+ max = 2;
+ }
+
+ const titleBonus = Math.max(0, Math.floor(titleLevel / 3));
+ return {
+ min: min + titleBonus,
+ max: max + titleBonus
+ };
+ }
+
+ calculateHouseholdOrder({ servantCount, servantQuality, servantPayLevel, houseType, character }) {
+ const expectation = this.getServantExpectation(houseType, character);
+ const missing = Math.max(0, expectation.min - servantCount);
+ const excessive = Math.max(0, servantCount - expectation.max);
+ const qualityPart = Math.round((Number(servantQuality || 0) - 50) * 0.35);
+ const payPart = this.getPayLevelQualityShift(servantPayLevel);
+ const fitPenalty = (missing * 10) + (excessive * 4);
+ return Math.max(0, Math.min(100, 55 + qualityPart + payPart - fitPenalty));
+ }
+
+ calculateServantMonthlyCost({ servantCount, servantQuality, servantPayLevel, houseType }) {
+ const basePerServant = Math.max(20, Math.round((Number(houseType?.cost || 0) / 1000) + 40));
+ const qualityFactor = 1 + ((Number(servantQuality || 50) - 50) / 200);
+ const payFactor = this.getPayLevelMultiplier(servantPayLevel);
+ return Math.round(servantCount * basePerServant * qualityFactor * payFactor * 100) / 100;
+ }
+
+ buildServantSummary(userHouse, character) {
+ const expectation = this.getServantExpectation(userHouse?.houseType, character);
+ const servantCount = Number(userHouse?.servantCount || 0);
+ const servantQuality = Number(userHouse?.servantQuality ?? 50);
+ const servantPayLevel = userHouse?.servantPayLevel || 'normal';
+ const householdOrder = Number(
+ userHouse?.householdOrder ??
+ this.calculateHouseholdOrder({
+ servantCount,
+ servantQuality,
+ servantPayLevel,
+ houseType: userHouse?.houseType,
+ character
+ })
+ );
+
+ let staffingState = 'fitting';
+ if (servantCount < expectation.min) staffingState = 'understaffed';
+ if (servantCount > expectation.max) staffingState = 'overstaffed';
+
+ let orderState = 'stable';
+ if (householdOrder < 35) orderState = 'chaotic';
+ else if (householdOrder < 55) orderState = 'strained';
+ else if (householdOrder > 80) orderState = 'excellent';
+
+ return {
+ expectedMin: expectation.min,
+ expectedMax: expectation.max,
+ monthlyCost: this.calculateServantMonthlyCost({
+ servantCount,
+ servantQuality,
+ servantPayLevel,
+ houseType: userHouse?.houseType
+ }),
+ staffingState,
+ orderState
+ };
+ }
+
+ async getOwnedUserHouse(hashedUserId) {
+ const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
+ const house = await UserHouse.findOne({
+ where: { userId: falukantUser.id },
+ include: [{ model: HouseType, as: 'houseType', attributes: ['id', 'position', 'cost', 'labelTr'] }]
+ });
+ if (!house) {
+ throw new Error('House not found');
+ }
+ return { falukantUser, house };
+ }
+
+ async hireServants(hashedUserId, amount = 1) {
+ const hireAmount = Math.max(1, Math.min(Number(amount) || 1, 10));
+ const { falukantUser, house } = await this.getOwnedUserHouse(hashedUserId);
+ const hireCost = Math.round(hireAmount * (40 + ((house.houseType?.cost || 0) / 2000)) * 100) / 100;
+ if (Number(falukantUser.money) < hireCost) {
+ throw new Error('notenoughmoney.');
+ }
+
+ house.servantCount = Number(house.servantCount || 0) + hireAmount;
+ house.servantQuality = Math.min(100, Number(house.servantQuality || 50) + Math.max(1, hireAmount));
+ house.householdOrder = this.calculateHouseholdOrder({
+ servantCount: house.servantCount,
+ servantQuality: house.servantQuality,
+ servantPayLevel: house.servantPayLevel,
+ houseType: house.houseType,
+ character: falukantUser.character
+ });
+ await house.save();
+ await updateFalukantUserMoney(falukantUser.id, -hireCost, 'servants_hired', falukantUser.id);
+
+ const user = await User.findByPk(falukantUser.userId);
+ notifyUser(user.hashedId, 'falukantHouseUpdate', {});
+ notifyUser(user.hashedId, 'falukantUpdateStatus', {});
+ return {
+ amount: hireAmount,
+ cost: hireCost,
+ servantSummary: this.buildServantSummary(house, falukantUser.character)
+ };
+ }
+
+ async dismissServants(hashedUserId, amount = 1) {
+ const dismissAmount = Math.max(1, Math.min(Number(amount) || 1, 10));
+ const { falukantUser, house } = await this.getOwnedUserHouse(hashedUserId);
+ const prevCount = Number(house.servantCount || 0);
+ if (prevCount <= 0) {
+ throw new Error('No servants to dismiss');
+ }
+
+ // Symmetrisch zu hireServants: Qualitätsänderung skaliert mit tatsächlich entlassener Anzahl
+ const actualDismissed = Math.min(dismissAmount, prevCount);
+ house.servantCount = Math.max(0, prevCount - dismissAmount);
+ house.servantQuality = Math.max(
+ 0,
+ Number(house.servantQuality || 50) - Math.max(1, actualDismissed)
+ );
+ house.householdOrder = this.calculateHouseholdOrder({
+ servantCount: house.servantCount,
+ servantQuality: house.servantQuality,
+ servantPayLevel: house.servantPayLevel,
+ houseType: house.houseType,
+ character: falukantUser.character
+ });
+ await house.save();
+
+ const user = await User.findByPk(falukantUser.userId);
+ notifyUser(user.hashedId, 'falukantHouseUpdate', {});
+ notifyUser(user.hashedId, 'falukantUpdateStatus', {});
+ return {
+ amount: dismissAmount,
+ servantSummary: this.buildServantSummary(house, falukantUser.character)
+ };
+ }
+
+ async setServantPayLevel(hashedUserId, payLevel) {
+ const normalizedPayLevel = ['low', 'normal', 'high'].includes(payLevel) ? payLevel : 'normal';
+ const { falukantUser, house } = await this.getOwnedUserHouse(hashedUserId);
+
+ const previousPayLevel = house.servantPayLevel || 'normal';
+ const oldShift = this.getPayLevelQualityShift(previousPayLevel);
+ const newShift = this.getPayLevelQualityShift(normalizedPayLevel);
+ const baseQuality = Number(house.servantQuality || 50) - oldShift;
+
+ house.servantPayLevel = normalizedPayLevel;
+ house.servantQuality = Math.max(0, Math.min(100, baseQuality + newShift));
+ house.householdOrder = this.calculateHouseholdOrder({
+ servantCount: house.servantCount,
+ servantQuality: house.servantQuality,
+ servantPayLevel: house.servantPayLevel,
+ houseType: house.houseType,
+ character: falukantUser.character
+ });
+ await house.save();
+
+ const user = await User.findByPk(falukantUser.userId);
+ notifyUser(user.hashedId, 'falukantHouseUpdate', {});
+ notifyUser(user.hashedId, 'falukantUpdateStatus', {});
+ return {
+ payLevel: normalizedPayLevel,
+ servantSummary: this.buildServantSummary(house, falukantUser.character)
+ };
+ }
+
async getPartyTypes(hashedUserId) {
const falukantUser = await getFalukantUserOrFail(hashedUserId);
const engagedCount = await Relationship.count({
diff --git a/backend/sql/add_servants_to_user_house.sql b/backend/sql/add_servants_to_user_house.sql
new file mode 100644
index 0000000..2cf25aa
--- /dev/null
+++ b/backend/sql/add_servants_to_user_house.sql
@@ -0,0 +1,7 @@
+-- PostgreSQL only
+
+ALTER TABLE falukant_data.user_house
+ ADD COLUMN IF NOT EXISTS servant_count integer NOT NULL DEFAULT 0,
+ ADD COLUMN IF NOT EXISTS servant_quality integer NOT NULL DEFAULT 50,
+ ADD COLUMN IF NOT EXISTS servant_pay_level varchar(20) NOT NULL DEFAULT 'normal',
+ ADD COLUMN IF NOT EXISTS household_order integer NOT NULL DEFAULT 55;
diff --git a/docs/FALUKANT_SERVANTS_CONCEPT.md b/docs/FALUKANT_SERVANTS_CONCEPT.md
new file mode 100644
index 0000000..12f1ffd
--- /dev/null
+++ b/docs/FALUKANT_SERVANTS_CONCEPT.md
@@ -0,0 +1,336 @@
+# Falukant: Konzept Dienerschaft
+
+Dieses Dokument beschreibt ein eigenständiges Dienerschaftssystem für Falukant. Die Dienerschaft hängt bewusst am Haus und nicht primär an Familie oder Liebschaften.
+
+## 1. Grundentscheidung
+
+Dienerschaft ist Teil des Hausstands.
+
+Warum:
+- Diener versorgen Haushalt, Gebäude, Gäste und Repräsentation.
+- Die Größe und Qualität der Dienerschaft hängt stärker an Hausgröße und Stand als an einzelnen Familienbeziehungen.
+- Spätere Systeme wie Diskretion, Skandalabwehr, Botengänge, Schutz und Festkultur lassen sich so an einer Stelle bündeln.
+
+Folgerung:
+- Hauptansicht: `HouseView`
+- Datenträger: `user_house` plus eigene Dienerstruktur
+- Familie, Liebschaften, Ruf und Untergrund nutzen die Effekte mit, besitzen das System aber nicht selbst.
+
+## 2. Spielziel
+
+Die Dienerschaft soll vier Dinge leisten:
+- laufende Kosten und Standesdruck erzeugen
+- Komfort und Ordnung des Haushalts darstellen
+- Repräsentation und Ansehen beeinflussen
+- Diskretion und Risiko in Familien- und Liebschaftsfragen mitsteuern
+
+Die erste Ausbaustufe bleibt bewusst einfach und abstrahiert. Einzelne Namen oder tiefes Personalmanagement kommen erst später.
+
+## 3. Kernmodell
+
+### 3.1 Erste Ausbaustufe: abstrakte Dienerschaft
+
+Der Spieler verwaltet keine einzelnen Diener, sondern einen Haushalt mit wenigen Zuständen:
+- `servantCount`
+- `servantQuality`
+- `servantPayLevel`
+- `householdOrder`
+
+Empfohlene Bedeutung:
+- `servantCount`: tatsächliche Zahl der Bediensteten
+- `servantQuality`: Ausbildungs- und Verlässlichkeitsniveau
+- `servantPayLevel`: wie gut der Haushalt bezahlt und versorgt wird
+- `householdOrder`: Ergebniswert für Disziplin, Sauberkeit, Organisation
+
+### 3.2 Spätere Ausbaustufe
+
+Erst später werden Rollen differenziert:
+- Hausverwalter / Haushofmeister
+- Kammerdiener / Zofen
+- Küchenpersonal
+- Stallpersonal
+- Kinder- und Pflegepersonal
+- Wachen / Torpersonal
+
+Diese zweite Stufe ist ausdrücklich nicht Teil des ersten Implementierungspakets.
+
+## 4. Verbindung zum Haus
+
+Die Dienerschaft ist an das Haus gekoppelt.
+
+Das Haus bestimmt:
+- maximal sinnvolle Dienerzahl
+- erwartete Mindestzahl je nach Stand
+- Ansehenswirkung von Über- oder Unterbesetzung
+- Kostenfaktor
+
+Ein kleines Haus mit zu großer Dienerschaft wirkt verschwenderisch.
+Ein großes oder nobles Haus mit zu wenig Dienern wirkt ungeordnet, geizig oder standeswidrig.
+
+## 5. Haus- und Standeslogik
+
+Die Zielgröße der Dienerschaft entsteht aus zwei Faktoren:
+- Hausgröße / Haustyp
+- gesellschaftlicher Stand
+
+### 5.1 Erwartungswert
+
+Jeder Haushalt hat einen erwarteten Bereich:
+- `expectedServantsMin`
+- `expectedServantsMax`
+
+Dieser Bereich wird aus Haus und Titel abgeleitet.
+
+Beispielhafte Richtung:
+- Holzhaus, niedriger Stand: 0 bis 1
+- kleines Familienhaus: 1 bis 3
+- Stadthaus oder höherer Adel: 3 bis 8
+- Hochadel und Hofnähe: deutlich darüber
+
+Wichtig:
+- Das sind keine finalen Balancing-Zahlen.
+- Das Balancing bleibt eine spätere Phase.
+
+## 6. Zentrale Spielwerte
+
+### 6.1 Dienerzahl
+
+Die Dienerzahl ist der wichtigste Primärwert.
+
+Zu wenig Diener:
+- schlechtere Haushaltsordnung
+- negativer Einfluss auf Ansehen in hohen Ständen
+- höhere Spannungen im Haus
+- weniger Diskretion und schwächerer Schutz vor Gerüchten
+
+Zu viele Diener:
+- unnötige Kosten
+- bei niedrigem Stand möglicher Vorwurf von Verschwendung oder Anmaßung
+- höheres Risiko für Klatsch, weil mehr Personen Wissen tragen
+
+### 6.2 Qualität
+
+Qualität beschreibt Verlässlichkeit und Niveau.
+
+Niedrige Qualität:
+- Haus funktioniert nur grob
+- Diskretion schlecht
+- Feste und Repräsentation schwächer
+- höheres Risiko für Gerede, Unordnung, Pannen
+
+Hohe Qualität:
+- besserer Hauszustand im Alltag
+- stärkere Diskretion
+- besserer Eindruck bei Gästen
+- positive Wirkung auf Ehekomfort und Familienruhe
+
+### 6.3 Bezahlung
+
+Die Bezahlung ist ein Steuerungswert.
+
+Niedrige Bezahlung:
+- spart kurzfristig Geld
+- senkt Loyalität und Qualität
+- erhöht Gerüchte- und Diebstahlrisiko
+
+Hohe Bezahlung:
+- kostet mehr
+- verbessert Loyalität, Qualität und Diskretion
+
+### 6.4 Haushaltsordnung
+
+`householdOrder` ist ein abgeleiteter Zustand.
+
+Er hängt ab von:
+- Dienerzahl im Verhältnis zur Sollgröße
+- Qualität
+- Bezahlung
+- Hauszustand
+
+Auswirkungen:
+- bessere Ordnung stabilisiert Ehe- und Familienwerte
+- schlechte Ordnung verschlechtert Komfort und Ansehen
+- sie beeinflusst spätere Fest- und Besuchssysteme
+
+## 7. Systemwirkungen
+
+### 7.1 Geld
+
+Dienerschaft erzeugt laufende Kosten.
+
+Monatliche Kosten hängen ab von:
+- Dienerzahl
+- Qualitätsniveau
+- Bezahlungsstufe
+- Hausgröße
+
+Später kann darin auch Nahrung, Kleidung und Ausstattung enthalten sein.
+
+### 7.2 Ansehen
+
+Ansehen wird nicht direkt nur durch „mehr Diener = besser“ berechnet.
+
+Stattdessen wirkt:
+- Passung zum Stand
+- Ordnung und Auftreten
+- offensichtliche Unterversorgung
+- offensichtliche Verschwendung
+
+Faustregel:
+- hohe Stände werden stärker nach Hausführung beurteilt
+- niedrige Stände dürfen einfacher leben
+- extreme Abweichungen nach oben oder unten wirken negativ
+
+### 7.3 Familie und Ehe
+
+Die Familie nutzt die Hauswirkung mit.
+
+Positive Effekte guter Dienerschaft:
+- mehr Komfort
+- geringere Alltagsbelastung
+- bessere Ehezufriedenheit
+- geringerer Haushaltsstress
+
+Negative Effekte schlechter Dienerschaft:
+- Unruhe im Haus
+- Streit über Kosten und Ordnung
+- zusätzliche Spannungen bei Ehe und Kindern
+
+### 7.4 Liebschaften und Skandale
+
+Dienerschaft beeinflusst Diskretion.
+
+Gut bezahlte, loyale und kleine, passende Dienerschaft:
+- schützt Geheimnisse besser
+- senkt Skandal- und Gerüchterisiko
+
+Unzufriedene oder zu große Dienerschaft:
+- erhöht Klatsch
+- macht verdeckte Beziehungen sichtbarer
+- verbessert die Chancen von Untergrundaktivitäten, etwas aufzudecken
+
+### 7.5 Untergrund / Aufdeckung
+
+Das Untergrundsystem soll später auf Dienerschaft zugreifen können.
+
+Beispiel:
+- unzufriedenes Personal erhöht Erfolg bei `investigate_affair`
+- sehr diskreter Haushalt erschwert Aufdeckung und Erpressung
+
+## 8. Standeslogik
+
+Die Bewertung der Dienerschaft ist standesabhängig.
+
+### Niedrige Stände
+
+Erlaubt:
+- kleine oder keine Dienerschaft
+
+Negativ:
+- zu große Dienerschaft bei kleinem Haus
+- demonstrative Übertreibung
+
+### Mittlere Stände
+
+Erwartet:
+- geordneter kleiner Haushalt
+- passende Grundversorgung
+
+Negativ:
+- sichtbare Unordnung
+- geizige Unterbesetzung
+- übertriebener Luxus
+
+### Hohe Stände
+
+Erwartet:
+- repräsentative, funktionierende Dienerschaft
+
+Negativ:
+- zu wenig Personal
+- schlechter Hauszustand trotz Rang
+- öffentlich erkennbare Überforderung im Haushalt
+
+## 9. UI-Richtung
+
+Die erste Oberfläche gehört in `HouseView`.
+
+Empfohlene Elemente:
+- Überblickskarte „Dienerschaft“
+- Ist-Zahl, Sollbereich, Qualität, Bezahlungsstufe, Haushaltsordnung
+- einfache Aktionen:
+ - Diener einstellen
+ - Diener entlassen
+ - Bezahlung anheben
+ - Bezahlung senken
+
+Zusätzliche Anzeigen:
+- erwarteter Bereich nach Haus und Stand
+- Monatskosten
+- Haupteffekte auf Ordnung, Ansehen und Diskretion
+
+Wichtig:
+- kein Mikromanagement pro Diener in der ersten Version
+- keine Personallisten im MVP
+
+## 10. Daemon-/Tick-Sicht
+
+Die eigentliche Veränderung der Zustände soll durch den externen Daemon laufen.
+
+Daily:
+- Drift von Loyalität und Ordnung
+- kleine Folgen schlechter Versorgung
+- Diskretionswirkung auf Familien- und Liebschaftssysteme
+
+Monthly:
+- Kosten abbuchen
+- Unterversorgung bewerten
+- Qualität und Loyalität nachziehen
+- Ansehenswirkung aus Passung und Ordnung anwenden
+
+## 11. MVP-Schnitt
+
+Erste spielbare Version:
+- Dienerschaft ist ein Hauswert
+- nur aggregierte Werte, keine Einzelrollen
+- UI in `HouseView`
+- monatliche Kosten
+- grobe Effekte auf:
+ - Haushaltsordnung
+ - Ansehen
+ - Ehezufriedenheit
+ - Diskretion bei Liebschaften
+
+Noch nicht im MVP:
+- benannte Diener
+- Intrigen einzelner Bediensteter
+- eigene Dienerereignisse mit langen Ketten
+- tiefes Rollenmanagement
+
+## 12. Spätere Ausbauten
+
+Später interessant:
+- Dienerschaft als Voraussetzung für bestimmte Feste
+- Spezialrollen wie Amme, Leibdiener, Spion im Haushalt
+- interne Konflikte unter Dienern
+- Diebstahl, Bestechung, Illoyalität
+- Hauspersonal als Quelle für Gerüchte oder Schutz
+- Untergrund kann Personal bestechen
+
+## 13. Offene Designentscheidungen
+
+1. Soll die erste Version mit einer absoluten `servantCount` arbeiten oder mit Stufen wie klein / passend / groß?
+2. Soll `householdOrder` direkt gespeichert oder komplett aus anderen Werten berechnet werden?
+3. Soll Bezahlung als Prozentwert, feste Stufe oder Freitext-Enum geführt werden?
+4. Wie stark soll Dienerschaft bereits in der ersten Version auf Liebschaften und Untergrund wirken?
+5. Sollen Feste weiter ihr eigenes `servantRatio` behalten oder später an das neue System angebunden werden?
+
+## 14. Empfehlung
+
+Empfohlene erste Umsetzung:
+- `servantCount` als absolute Zahl
+- `servantQuality` als einfacher Wert 0 bis 100
+- `servantPayLevel` als feste Stufen `low`, `normal`, `high`
+- `householdOrder` als gespeicherter, vom Daemon gepflegter Zustand
+
+Diese Variante ist einfach genug für ein erstes Spielsystem, aber stark genug, um später Familie, Ruf, Untergrund und Feste daran anzuschließen.
diff --git a/docs/FALUKANT_SERVANTS_IMPLEMENTATION_SPEC.md b/docs/FALUKANT_SERVANTS_IMPLEMENTATION_SPEC.md
new file mode 100644
index 0000000..8f0e41a
--- /dev/null
+++ b/docs/FALUKANT_SERVANTS_IMPLEMENTATION_SPEC.md
@@ -0,0 +1,628 @@
+# 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.
diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json
index 24e9fdd..02d3e59 100644
--- a/frontend/src/i18n/locales/de/falukant.json
+++ b/frontend/src/i18n/locales/de/falukant.json
@@ -826,8 +826,51 @@
"price": "Kaufpreis",
"worth": "Restwert",
"sell": "Verkaufen",
+ "sellConfirm": "Möchtest du dein Haus wirklich verkaufen?",
+ "sellSuccess": "Das Haus wurde verkauft.",
+ "sellError": "Das Haus konnte nicht verkauft werden.",
+ "buySuccess": "Das Haus wurde gekauft.",
+ "buyError": "Das Haus konnte nicht gekauft werden.",
"renovate": "Renovieren",
"renovateAll": "Komplett renovieren",
+ "servants": {
+ "title": "Dienerschaft",
+ "description": "Verwalte Hauspersonal, Ordnung und laufende Kosten deines Haushalts.",
+ "count": "Dienerzahl",
+ "expectedRange": "Erwarteter Bereich",
+ "monthlyCost": "Monatskosten",
+ "quality": "Qualität",
+ "householdOrder": "Haushaltsordnung",
+ "payLevel": "Bezahlung",
+ "payLevels": {
+ "low": "Niedrig",
+ "normal": "Normal",
+ "high": "Großzügig"
+ },
+ "staffingState": {
+ "label": "Besetzung",
+ "understaffed": "Unterbesetzt",
+ "fitting": "Passend",
+ "overstaffed": "Überbesetzt"
+ },
+ "orderState": {
+ "label": "Ordnungszustand",
+ "chaotic": "Chaotisch",
+ "strained": "Angespannt",
+ "stable": "Stabil",
+ "excellent": "Vorbildlich"
+ },
+ "actions": {
+ "hire": "1 Diener einstellen",
+ "dismiss": "1 Diener entlassen",
+ "hireSuccess": "Die Dienerschaft wurde erweitert.",
+ "hireError": "Die Dienerschaft konnte nicht erweitert werden.",
+ "dismissSuccess": "Ein Diener wurde entlassen.",
+ "dismissError": "Der Diener konnte nicht entlassen werden.",
+ "payLevelSuccess": "Die Bezahlung der Dienerschaft wurde angepasst.",
+ "payLevelError": "Die Bezahlung konnte nicht angepasst werden."
+ }
+ },
"status": {
"roofCondition": "Dach",
"wallCondition": "Wände",
diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json
index f8c96de..2442cf7 100644
--- a/frontend/src/i18n/locales/en/falukant.json
+++ b/frontend/src/i18n/locales/en/falukant.json
@@ -149,7 +149,7 @@
"all": "All history"
}
},
- "activities": {
+ "activities": {
"Product sale": "Product sale",
"Production cost": "Production cost",
"Sell all products": "Sell all products",
@@ -181,6 +181,75 @@
}
}
},
+ "house": {
+ "title": "House",
+ "statusreport": "House condition",
+ "element": "Element",
+ "state": "Condition",
+ "buyablehouses": "Buy a house",
+ "buy": "Buy",
+ "price": "Purchase price",
+ "worth": "Residual value",
+ "sell": "Sell",
+ "sellConfirm": "Do you really want to sell your house?",
+ "sellSuccess": "The house has been sold.",
+ "sellError": "The house could not be sold.",
+ "buySuccess": "The house has been bought.",
+ "buyError": "The house could not be bought.",
+ "renovate": "Renovate",
+ "renovateAll": "Renovate completely",
+ "servants": {
+ "title": "Servants",
+ "description": "Manage household staff, order and recurring costs in your home.",
+ "count": "Servant count",
+ "expectedRange": "Expected range",
+ "monthlyCost": "Monthly cost",
+ "quality": "Quality",
+ "householdOrder": "Household order",
+ "payLevel": "Pay level",
+ "payLevels": {
+ "low": "Low",
+ "normal": "Normal",
+ "high": "Generous"
+ },
+ "staffingState": {
+ "label": "Staffing",
+ "understaffed": "Understaffed",
+ "fitting": "Fitting",
+ "overstaffed": "Overstaffed"
+ },
+ "orderState": {
+ "label": "Order state",
+ "chaotic": "Chaotic",
+ "strained": "Strained",
+ "stable": "Stable",
+ "excellent": "Excellent"
+ },
+ "actions": {
+ "hire": "Hire 1 servant",
+ "dismiss": "Dismiss 1 servant",
+ "hireSuccess": "The household staff has been expanded.",
+ "hireError": "The staff could not be expanded.",
+ "dismissSuccess": "A servant has been dismissed.",
+ "dismissError": "The servant could not be dismissed.",
+ "payLevelSuccess": "Servant pay has been updated.",
+ "payLevelError": "Servant pay could not be updated."
+ }
+ },
+ "status": {
+ "roofCondition": "Roof",
+ "wallCondition": "Walls",
+ "floorCondition": "Floors",
+ "windowCondition": "Windows"
+ },
+ "type": {
+ "backyard_room": "Backyard room",
+ "wooden_house": "Wooden house",
+ "straw_hut": "Straw hut",
+ "family_house": "Family house",
+ "townhouse": "Townhouse"
+ }
+ },
"newdirector": {
"title": "New Director",
"age": "Age",
diff --git a/frontend/src/i18n/locales/es/falukant.json b/frontend/src/i18n/locales/es/falukant.json
index 09e9b13..a5afa70 100644
--- a/frontend/src/i18n/locales/es/falukant.json
+++ b/frontend/src/i18n/locales/es/falukant.json
@@ -792,8 +792,51 @@
"price": "Precio de compra",
"worth": "Valor restante",
"sell": "Vender",
+ "sellConfirm": "¿De verdad quieres vender tu casa?",
+ "sellSuccess": "La casa ha sido vendida.",
+ "sellError": "No se pudo vender la casa.",
+ "buySuccess": "La casa ha sido comprada.",
+ "buyError": "No se pudo comprar la casa.",
"renovate": "Renovar",
"renovateAll": "Renovar por completo",
+ "servants": {
+ "title": "Servicio doméstico",
+ "description": "Administra el personal, el orden y los costes periódicos de tu casa.",
+ "count": "Número de sirvientes",
+ "expectedRange": "Rango esperado",
+ "monthlyCost": "Coste mensual",
+ "quality": "Calidad",
+ "householdOrder": "Orden del hogar",
+ "payLevel": "Pago",
+ "payLevels": {
+ "low": "Bajo",
+ "normal": "Normal",
+ "high": "Generoso"
+ },
+ "staffingState": {
+ "label": "Dotación",
+ "understaffed": "Insuficiente",
+ "fitting": "Adecuada",
+ "overstaffed": "Excesiva"
+ },
+ "orderState": {
+ "label": "Estado del orden",
+ "chaotic": "Caótico",
+ "strained": "Tenso",
+ "stable": "Estable",
+ "excellent": "Excelente"
+ },
+ "actions": {
+ "hire": "Contratar 1 sirviente",
+ "dismiss": "Despedir 1 sirviente",
+ "hireSuccess": "Se ha ampliado el servicio doméstico.",
+ "hireError": "No se pudo ampliar el servicio doméstico.",
+ "dismissSuccess": "Se ha despedido a un sirviente.",
+ "dismissError": "No se pudo despedir al sirviente.",
+ "payLevelSuccess": "Se ha ajustado el pago del servicio.",
+ "payLevelError": "No se pudo ajustar el pago."
+ }
+ },
"status": {
"roofCondition": "Techo",
"wallCondition": "Paredes",
diff --git a/frontend/src/views/falukant/HouseView.vue b/frontend/src/views/falukant/HouseView.vue
index 3695e57..b9a8129 100644
--- a/frontend/src/views/falukant/HouseView.vue
+++ b/frontend/src/views/falukant/HouseView.vue
@@ -34,6 +34,65 @@
+ {{ $t('falukant.house.servants.description') }}{{ $t('falukant.house.servants.title') }}
+