// ALLE Spielinhalte data-driven: Besuchergruppen, Infrastrukturkatalog, // Phasen-Wellen, Ereigniskarten, Entscheidungen, Feed-Texte, Regionsprofile. // Zahlen folgen dem Pflichtenheft (§7, §10, §11, §20) und wurden mit dem // Headless-Modelltest (tests/model-test.mjs) kalibriert. export const GAME_LEN = 1200; // 20 Minuten Echtzeit (Sekunden) export const PHASE_LEN = 150; // 8 Phasen à 2:30 export const SEASON_LEN = 300; // 4 Saisonen à 5:00 export const SEASONS = ['Frühling', 'Sommer', 'Herbst', 'Winter']; export const SEASON_KEYS = ['spring', 'summer', 'autumn', 'winter']; export const CONTACT_NORM = 5; // Normkontaktzeit in Sekunden (§11.6) export const MAX_GAIN_FACTOR = 1.2; // Deckel je Attraktion (× Grundwirkung) export const MAX_BRAKE = 0.6; // Maximalbremse (§11.5) export const START_BUDGET = 500; export const START_ENV = 75; // ---------- Besuchergruppen (§7.4) ---------- // speed in Kacheln/s · budget = max. Ausgaben · satTarget = Zufriedenheitsziel // entry: W = Straße West, N = Radweg Nord, S = Fußweg Süd export const GROUPS = { spaziergaenger: { name: 'Spaziergänger', emoji: '🚶', color: '#3f8f6b', size: 2, speed: 0.55, budget: 30, satTarget: 60, maxTime: 160, envSens: 0.8, trafficSens: 0.6, transport: 'foot', entry: 'S', needsService: false, bergAffinity: 0.1, lakeAffinity: 0.7, likes: 'Natur · Café · Aussicht', wish: 'ein gemütliches Café', who: 'Einheimische und Gäste aus der Umgebung, oft Paare auf Tagesausflug. Kleines Budget (~30 €), aber wichtig für den Anfang: Sie erzählen weiter, wie schön es bei euch ist.', fromPhase: 1, }, wanderer: { name: 'Wanderer', emoji: '🥾', color: '#2f6ea0', size: 2, speed: 0.62, budget: 45, satTarget: 75, maxTime: 200, envSens: 1.0, trafficSens: 0.5, transport: 'foot', entry: 'S', needsService: false, bergAffinity: 0.9, lakeAffinity: 0.4, likes: 'Natur · Aussicht · Gasthaus', wish: 'ein Weg auf den Berg', who: 'Naturfreunde mit Rucksack, kommen zu Fuß über den Südweg. Mittleres Budget (~45 €), sehr umweltbewusst – eine kaputte Landschaft vergrault sie sofort. Träumen von einem Weg auf den Berg.', fromPhase: 1, }, familie: { name: 'Familie', emoji: '👨‍👩‍👧‍👦', color: '#e8892b', size: 4, speed: 0.5, budget: 90, satTarget: 100, maxTime: 180, envSens: 0.7, trafficSens: 0.8, transport: 'car', entry: 'W', needsService: true, bergAffinity: 0.25, lakeAffinity: 0.9, likes: 'Spielplatz · See · Eis · WC', wish: 'ein Spielplatz für die Kinder', who: 'Eltern mit zwei Kindern aus der Stadt, kommen mit dem Auto über die Weststraße. Solides Budget (~90 €), aber klare Ansprüche: Ohne Spielplatz, WC und Abkühlung am See wird der Ausflug zum Stresstest.', fromPhase: 2, }, radfahrer: { name: 'Radfahrer', emoji: '🚴', color: '#c9531f', size: 2, speed: 1.35, budget: 40, satTarget: 60, maxTime: 90, envSens: 0.8, trafficSens: 0.9, transport: 'bike', entry: 'N', needsService: false, bergAffinity: 0.15, lakeAffinity: 0.4, likes: 'Radstation · Café · Aussicht', wish: 'eine Radreparaturstation', who: 'Sportliche Gäste auf dem Radweg von Norden – schnell unterwegs! Angebote wirken nur direkt an der Strecke. Kleines Budget (~40 €): Reparaturstation, Café, kurze Stopps.', fromPhase: 3, }, senioren: { name: 'Seniorengruppe', emoji: '👵', color: '#a34a8e', size: 3, speed: 0.38, budget: 80, satTarget: 90, maxTime: 220, envSens: 0.6, trafficSens: 0.9, transport: 'car', entry: 'W', needsService: true, bergAffinity: 0.15, lakeAffinity: 0.5, likes: 'Café · Kultur · Aussicht · Sitzbänke', wish: 'ein Museum oder Café', who: 'Reiselustige Pensionistinnen und Pensionisten mit gutem Budget (~80 €) und viel Zeit. Gehen langsam, mögen keinen Verkehrslärm – dafür Café, Kultur und schöne Ausblicke.', fromPhase: 5, }, schulklasse: { name: 'Schulklasse', emoji: '🎒', color: '#2e8b8b', size: 6, speed: 0.6, budget: 25, satTarget: 80, maxTime: 170, envSens: 0.5, trafficSens: 0.4, transport: 'bus', entry: 'S', needsService: true, bergAffinity: 0.4, lakeAffinity: 0.8, likes: 'Erlebnis · Bildung · günstige Jause', wish: 'etwas zum Entdecken (Museum, Spielplatz)', who: 'Eine Klasse mit Lehrpersonen auf Exkursion – knappes Budget (~25 €), aber die Gäste von morgen! Brauchen Erlebnis, etwas zum Lernen (Museum!) und eine günstige Jause.', fromPhase: 5, }, busgruppe: { name: 'Busreisegruppe', emoji: '🚌', color: '#8c6239', size: 6, speed: 0.45, budget: 70, satTarget: 95, maxTime: 150, envSens: 0.4, trafficSens: 0.5, transport: 'bus', entry: 'W', needsService: true, needsParking: true, bergAffinity: 0.3, lakeAffinity: 0.4, likes: 'Sehenswürdigkeit · Souvenirs · Gasthaus · WC', wish: 'ein Busparkplatz und ein WC', who: 'Organisierte Tagesreisende mit Reiseleitung, mittleres Budget (~70 €). Achtung: Ohne Busparkplatz fährt der Bus einfach weiter! Programm: Sehenswürdigkeit, Souvenirs, Gasthaus, WC.', fromPhase: 5, }, junge_gruppe: { name: 'Junge Reisegruppe', emoji: '🤸', color: '#5b6ee1', size: 4, speed: 0.8, budget: 60, satTarget: 105, maxTime: 170, envSens: 0.5, trafficSens: 0.3, transport: 'car', entry: 'W', needsService: false, bergAffinity: 0.95, lakeAffinity: 0.6, likes: 'Action · Berg · Aussicht · günstige Gastro', wish: 'Action am Berg (Skilift!)', who: 'Freundesgruppen auf der Suche nach Action am Berg. Mittleres Budget (~60 €), unempfindlich gegen Trubel – aber ohne Lift und Erlebnis ziehen sie weiter.', fromPhase: 6, }, luxuspaar: { name: 'Wohlhabendes Paar', emoji: '💎', color: '#b0356b', size: 2, speed: 0.5, budget: 200, satTarget: 140, maxTime: 200, envSens: 0.9, trafficSens: 0.8, transport: 'car', entry: 'W', needsService: false, bergAffinity: 0.6, lakeAffinity: 0.5, likes: 'feines Restaurant · Wellness · Aussicht · Kultur', wish: 'ein wirklich gutes Restaurant (Wirtshaus Stufe 4+)', who: 'Wohlhabende Paare mit hohen Erwartungen und großem Budget (~200 €). Sie kommen nur, wenn die Qualität stimmt: feines Restaurant (Wirtshaus Stufe 4+), Wellness, Kultur – und eine intakte Landschaft.', fromPhase: 7, }, }; // ---------- Infrastrukturkatalog (§9/§10) ---------- // Arrays sind je Ausbaustufe (Index 0 = Stufe 1). // cost[0] = Baukosten, cost[i] = Kosten des Upgrades auf Stufe i+1. // sat = Grundwirkung Zufriedenheit je Normkontakt · slow = Bremse (Anteil) // opCost = Betriebskosten pro Minute · revenue = Basisumsatz je Normkontakt // env = Umweltwirkung beim Erreichen der Stufe (Delta) · range in Kacheln // target = Zielgruppenfaktor (fehlend ⇒ 0.4) · season = Saisonfaktor export const BUILDINGS = { wirtshaus: { name: 'Wirtshaus', emoji: '🍽️', cat: 'gastronomy', unlockPhase: 1, item: '🍲', itemsBy: { luxuspaar: '🍷', schulklasse: '🍟' }, sound: 'bite', desc: 'Stärkt fast alle Gäste. Solides Startgebäude.', levelNames: ['Wirtshaus', 'Gasthof', 'Landgasthof', 'Restaurant', 'Gourmetrestaurant'], cost: [100, 180, 320, 520, 900], sat: [8, 13, 20, 28, 40], slow: [0.02, 0.04, 0.06, 0.08, 0.1], range: [4, 4.5, 5, 5.5, 6], capacity: [5, 8, 12, 18, 25], opCost: [4, 7, 12, 20, 35], revenue: [7, 10, 15, 22, 32], env: [-1, -1, -1, -1, -2], target: { wanderer: 1.2, spaziergaenger: 1.1, familie: 0.8, senioren: 1.1, schulklasse: 0.6, busgruppe: 1.0, radfahrer: 0.9, junge_gruppe: 0.9, luxuspaar: 0.3, }, targetByLevel: { luxuspaar: [0.3, 0.3, 0.3, 1.2, 1.3] }, season: { spring: 1, summer: 1, autumn: 1.1, winter: 1.2 }, place: { near: 'path' }, }, cafe: { name: 'Café', emoji: '☕', cat: 'gastronomy', unlockPhase: 1, item: '☕', itemsBy: { familie: '🧃', schulklasse: '🧃', senioren: '🍰' }, sound: 'sip', desc: 'Bremst und erfreut – besonders Senioren, Spaziergänger, Paare.', cost: [80, 140, 240, 400, 650], sat: [5, 8, 12, 17, 24], slow: [0.10, 0.15, 0.20, 0.25, 0.30], range: [3.5, 4, 4.5, 5, 5.5], capacity: [5, 7, 10, 14, 20], opCost: [3, 5, 8, 13, 20], revenue: [5, 7, 10, 14, 20], env: [-1, 0, -1, -1, -1], target: { senioren: 1.4, spaziergaenger: 1.3, luxuspaar: 1.0, familie: 0.9, radfahrer: 1.1, wanderer: 0.9, junge_gruppe: 0.8, busgruppe: 0.8, schulklasse: 0.5, }, season: { spring: 1.1, summer: 1, autumn: 1.1, winter: 1 }, place: { near: 'path' }, }, marktstand: { name: 'Marktstand', emoji: '🧺', cat: 'shopping', unlockPhase: 1, item: '🍎', itemsBy: { familie: '🧺', busgruppe: '🧺' }, sound: 'bite', desc: 'Reine Bremse: Gäste bleiben länger, kleiner Umsatz.', cost: [60, 100, 170, 280, 450], sat: [1, 2, 3, 4, 5], slow: [0.08, 0.12, 0.17, 0.22, 0.28], range: [3, 3.5, 4, 4.5, 5], capacity: [8, 12, 16, 22, 30], opCost: [2, 3, 5, 8, 12], revenue: [3, 5, 7, 10, 14], env: [0, 0, 0, -1, -1], target: { spaziergaenger: 1.1, senioren: 1.2, familie: 1.0, busgruppe: 1.2, wanderer: 0.8, luxuspaar: 0.8, schulklasse: 0.7, junge_gruppe: 0.8, radfahrer: 0.5, }, season: { spring: 1, summer: 1.1, autumn: 1.2, winter: 0.9 }, place: { near: 'path' }, }, aussicht: { name: 'Aussichtspunkt', emoji: '🔭', cat: 'nature', unlockPhase: 1, fx: '📷', sound: 'click', desc: 'Landschaftserlebnis. Wirkt stärker in erhöhter Lage.', cost: [90, 160, 280, 470, 780], sat: [7, 12, 18, 26, 36], slow: [0.04, 0.05, 0.06, 0.07, 0.08], range: [4, 4.5, 5, 5.5, 6], capacity: [8, 12, 16, 22, 30], opCost: [1, 2, 3, 5, 8], revenue: [0, 0, 1, 1, 2], env: [0, 0, 0, -1, -1], target: { wanderer: 1.4, senioren: 1.2, luxuspaar: 1.2, spaziergaenger: 1.2, busgruppe: 1.1, familie: 0.8, radfahrer: 0.9, junge_gruppe: 1.0, schulklasse: 0.8, }, season: { spring: 1, summer: 1.1, autumn: 1.2, winter: 1.1 }, place: { near: 'path' }, elevBonus: true, natureBoosted: true, }, spielplatz: { name: 'Spielplatz', emoji: '🛝', cat: 'play', unlockPhase: 2, fx: '🎈', sound: 'pling', desc: 'Familien-Magnet, auch Schulklassen. Kaum Umsatz, viel Freude.', cost: [120, 200, 340, 560, 900], sat: [12, 18, 26, 35, 45], slow: [0.08, 0.10, 0.12, 0.14, 0.16], range: [4, 4.5, 5, 5.5, 6], capacity: [6, 9, 13, 18, 25], opCost: [2, 3, 5, 8, 12], revenue: [0, 0, 0, 1, 1], env: [0, 0, 0, -1, -1], target: { familie: 1.0, schulklasse: 0.7, spaziergaenger: 0.3, junge_gruppe: 0.4 }, season: { spring: 1.1, summer: 1.2, autumn: 1, winter: 0.7 }, place: { near: 'path' }, }, wc: { name: 'WC / Servicepunkt', emoji: '🚻', cat: 'service', unlockPhase: 2, maxLevel: 3, desc: 'Unauffällig, aber wichtig: verhindert Frust bei langen Aufenthalten.', cost: [60, 110, 190], sat: [2, 3, 4], slow: [0, 0, 0], range: [5, 6, 7], capacity: [12, 20, 30], opCost: [2, 3, 5], revenue: [0, 0, 0], env: [0, 0, 0], target: { familie: 1.2, senioren: 1.2, busgruppe: 1.2, schulklasse: 1.0 }, season: { spring: 1, summer: 1, autumn: 1, winter: 1 }, place: { near: 'path' }, service: true, }, badesteg: { name: 'Badesteg', emoji: '🏖️', cat: 'nature', unlockPhase: 2, fx: '🏊', sound: 'splash', desc: 'Badespaß am See – im Sommer der Hit, im Winter tot.', cost: [110, 190, 320, 530, 860], sat: [8, 13, 19, 27, 37], slow: [0.10, 0.12, 0.15, 0.18, 0.22], range: [4, 4.5, 5, 5.5, 6], capacity: [6, 9, 13, 18, 25], opCost: [2, 3, 5, 8, 12], revenue: [2, 3, 4, 6, 8], env: [-1, -1, -1, -2, -2], target: { familie: 1.3, junge_gruppe: 1.0, schulklasse: 1.0, spaziergaenger: 0.8, wanderer: 0.5, luxuspaar: 0.6, }, season: { spring: 0.4, summer: 1.6, autumn: 0.3, winter: 0.1 }, place: { near: 'lake' }, natureBoosted: true, }, eisdiele: { name: 'Eisdiele', emoji: '🍦', cat: 'gastronomy', unlockPhase: 3, item: '🍦', sound: 'bite', desc: 'Süße Bremse für Familien und Kinder, stark im Sommer.', cost: [90, 150, 260, 430, 700], sat: [6, 9, 13, 18, 25], slow: [0.08, 0.11, 0.14, 0.17, 0.20], range: [3.5, 4, 4.5, 5, 5.5], capacity: [6, 9, 13, 18, 25], opCost: [2, 4, 6, 10, 15], revenue: [4, 6, 9, 13, 18], env: [0, 0, -1, -1, -1], target: { familie: 1.4, schulklasse: 1.2, junge_gruppe: 1.0, spaziergaenger: 1.0, radfahrer: 1.0, senioren: 0.9, }, season: { spring: 0.9, summer: 1.5, autumn: 0.7, winter: 0.3 }, place: { near: 'path' }, }, radstation: { name: 'Radreparaturstation', emoji: '🚲', cat: 'service', unlockPhase: 3, item: '🔧', sound: 'tool', desc: 'Wirkt fast nur auf Radfahrer – dafür richtig stark.', cost: [70, 120, 210, 350, 570], sat: [15, 23, 32, 43, 56], slow: [0.10, 0.12, 0.14, 0.16, 0.18], range: [3.5, 4, 4.5, 5, 5.5], capacity: [4, 6, 9, 13, 18], opCost: [1, 2, 3, 5, 8], revenue: [2, 3, 5, 7, 10], env: [0, 0, 0, 0, 0], target: { radfahrer: 1.0, junge_gruppe: 0.3 }, season: { spring: 1.1, summer: 1.2, autumn: 1, winter: 0.4 }, place: { near: 'path' }, }, tourismusinfo: { name: 'Tourismusinfo', emoji: 'ℹ️', cat: 'service', unlockPhase: 3, maxLevel: 5, desc: 'Multiplikator: verstärkt alle Attraktionen in ihrer Nähe.', cost: [130, 220, 370, 600, 950], sat: [2, 3, 4, 5, 6], slow: [0.03, 0.04, 0.05, 0.06, 0.07], range: [5, 5.5, 6, 6.5, 7], capacity: [10, 14, 20, 28, 40], opCost: [3, 5, 8, 12, 18], revenue: [0, 0, 0, 0, 0], env: [0, 0, 0, 0, 0], boost: [0.05, 0.10, 0.16, 0.23, 0.32], // +% Wirkung benachbarter Gebäude target: { busgruppe: 1.0, senioren: 1.0, familie: 0.8 }, season: { spring: 1, summer: 1, autumn: 1, winter: 1 }, place: { near: 'path' }, }, naturschutz: { name: 'Naturschutzgebiet', emoji: '🌿', cat: 'nature', unlockPhase: 4, maxLevel: 1, fx: '🦋', sound: 'pling', desc: 'Umwelt +20, Naturerlebnis +15 % – sperrt aber Bauplätze im Umkreis.', cost: [300], sat: [6], slow: [0.05], range: [5], capacity: [999], opCost: [2], revenue: [0], env: [20], target: { wanderer: 1.3, spaziergaenger: 1.1, luxuspaar: 0.9, familie: 0.7, schulklasse: 0.9 }, season: { spring: 1.2, summer: 1.1, autumn: 1.1, winter: 0.8 }, place: { near: 'any', notVillage: true }, blocksRadius: 2.5, }, museum: { name: 'Heimatmuseum', emoji: '🏛️', cat: 'culture', unlockPhase: 5, item: '🎟️', itemsBy: { schulklasse: '📚' }, sound: 'cash', desc: 'Kultur für Senioren, Schulklassen und Busgruppen.', cost: [200, 340, 560, 900, 1400], sat: [10, 15, 22, 30, 40], slow: [0.08, 0.10, 0.12, 0.14, 0.16], range: [4, 4.5, 5, 5.5, 6], capacity: [6, 9, 13, 18, 25], opCost: [4, 6, 10, 15, 22], revenue: [3, 5, 7, 10, 14], env: [0, 0, 0, 0, 0], target: { senioren: 1.3, schulklasse: 1.2, busgruppe: 1.2, luxuspaar: 1.0, wanderer: 0.5, familie: 0.6, }, season: { spring: 1, summer: 0.9, autumn: 1.2, winter: 1.2 }, place: { near: 'path' }, }, parkplatz: { name: 'Parkplatz', emoji: '🅿️', cat: 'service', unlockPhase: 5, desc: 'Bringt mehr Auto- und Busgäste – erzeugt aber Verkehr und kostet Umwelt.', cost: [150, 260, 430, 700, 1100], sat: [1, 1, 2, 2, 3], slow: [0, 0, 0, 0, 0], range: [5, 5.5, 6, 6.5, 7], capacity: [999, 999, 999, 999, 999], opCost: [2, 3, 5, 8, 12], revenue: [2, 3, 5, 7, 10], env: [-8, -3, -3, -4, -5], visitorPlus: [0.05, 0.10, 0.17, 0.25, 0.35], // + Anteil Auto-/Busgäste traffic: [5, 10, 17, 28, 42], target: { busgruppe: 1.2, familie: 0.8, senioren: 0.8, luxuspaar: 0.6 }, season: { spring: 1, summer: 1, autumn: 1, winter: 1 }, place: { near: 'road' }, }, bushaltestelle: { name: 'Bushaltestelle', emoji: '🚏', cat: 'service', unlockPhase: 5, maxLevel: 5, desc: 'Nachhaltige Anreise: mehr Gruppen, kaum Umweltbelastung.', cost: [120, 210, 350, 570, 900], sat: [1, 1, 2, 2, 3], slow: [0, 0, 0, 0, 0], range: [5, 5.5, 6, 6.5, 7], capacity: [999, 999, 999, 999, 999], opCost: [3, 5, 8, 12, 18], revenue: [1, 2, 3, 4, 6], env: [1, 1, 1, 1, 1], busPlus: [0.04, 0.08, 0.14, 0.20, 0.28], // + Anteil Bus-Gäste target: { busgruppe: 1.0, senioren: 1.0, schulklasse: 1.0 }, season: { spring: 1, summer: 1, autumn: 1, winter: 1 }, place: { near: 'road' }, }, souvenir: { name: 'Souvenirshop', emoji: '🎁', cat: 'shopping', unlockPhase: 5, item: '🎁', itemsBy: { junge_gruppe: '🧢' }, sound: 'cash', desc: 'Bremse + Umsatz, geliebt von Busgruppen.', cost: [100, 170, 290, 480, 780], sat: [4, 6, 9, 13, 18], slow: [0.10, 0.13, 0.16, 0.20, 0.24], range: [3.5, 4, 4.5, 5, 5.5], capacity: [6, 9, 13, 18, 25], opCost: [2, 4, 6, 10, 15], revenue: [5, 7, 10, 14, 20], env: [0, 0, -1, -1, -1], target: { busgruppe: 1.4, senioren: 1.0, familie: 0.8, luxuspaar: 0.7, schulklasse: 0.8, junge_gruppe: 0.6, }, season: { spring: 1, summer: 1.1, autumn: 1.1, winter: 1.1 }, place: { near: 'path' }, }, skilift: { name: 'Skilift', emoji: '🚡', cat: 'sport', unlockPhase: 6, fx: '🎿', sound: 'pling', desc: 'Erschließt den Berg: neuer Rundweg + Bauplätze oben. Teuer, stark im Winter, Umwelteingriff.', levelNames: ['Schlepplift', 'Sessellift', '6er-Sessellift', 'Gondelbahn', 'Erlebnisbahn'], cost: [800, 1300, 2100, 3500, 5500], sat: [18, 26, 36, 48, 62], slow: [0, 0, 0, 0, 0], range: [4, 4.5, 5, 5.5, 6], capacity: [8, 14, 22, 32, 45], opCost: [15, 22, 32, 45, 60], revenue: [10, 14, 20, 28, 40], env: [-10, -2, -2, -3, -3], target: { junge_gruppe: 1.3, wanderer: 1.0, familie: 0.7, schulklasse: 0.9, luxuspaar: 0.8, senioren: 0.3, }, targetByLevel: { senioren: [0.3, 0.3, 0.5, 1.0, 1.0] }, // Gondel hilft Senioren season: { spring: 0.5, summer: 0.7, autumn: 0.6, winter: 1.5 }, place: { zone: 'hang', unique: true }, opensBerg: true, }, almhuette: { name: 'Almhütte', emoji: '🏔️', cat: 'gastronomy', unlockPhase: 6, item: '🧀', itemsBy: { wanderer: '🥨', junge_gruppe: '🥤' }, sound: 'bite', desc: 'Einkehr am Berg – nur sinnvoll, wenn der Lift Gäste bringt.', cost: [180, 300, 500, 820, 1300], sat: [10, 15, 22, 30, 40], slow: [0.10, 0.13, 0.16, 0.20, 0.24], range: [4, 4.5, 5, 5.5, 6], capacity: [5, 8, 12, 18, 25], opCost: [4, 6, 10, 15, 22], revenue: [6, 9, 13, 19, 27], env: [-1, -1, -1, -1, -2], target: { wanderer: 1.3, junge_gruppe: 1.2, luxuspaar: 0.9, schulklasse: 0.8, familie: 0.8, senioren: 0.9 }, season: { spring: 0.8, summer: 1.1, autumn: 1.1, winter: 1.4 }, place: { zone: 'berg', needsLift: true }, }, wellness: { name: 'Wellnesshaus', emoji: '💆', cat: 'culture', unlockPhase: 7, item: '🌺', itemsBy: { luxuspaar: '💆' }, sound: 'sip', desc: 'Ruhe und Luxus für anspruchsvolle Gäste.', cost: [600, 1000, 1600, 2600, 4200], sat: [14, 20, 28, 38, 50], slow: [0.12, 0.14, 0.17, 0.20, 0.24], range: [4, 4.5, 5, 5.5, 6], capacity: [4, 6, 9, 13, 18], opCost: [8, 12, 18, 26, 38], revenue: [12, 17, 24, 34, 48], env: [-3, -1, -2, -2, -2], target: { luxuspaar: 1.4, senioren: 0.9, familie: 0.3, junge_gruppe: 0.4 }, season: { spring: 1, summer: 0.9, autumn: 1.1, winter: 1.3 }, place: { near: 'path' }, }, }; for (const def of Object.values(BUILDINGS)) { def.maxLevel = def.maxLevel ?? def.cost.length; } // ---------- Wellen je Phase (§20) ---------- // Basisanzahl; skaliert mit Besucherfaktor (0.7 + Ø-Sterne/5) und // Parkplatz-/Bushaltestellen-Bonus für Auto-/Busgruppen. export const WAVES = { 1: { spaziergaenger: 5, wanderer: 3 }, 2: { spaziergaenger: 5, wanderer: 4, familie: 2 }, 3: { spaziergaenger: 3, wanderer: 3, familie: 5, radfahrer: 4 }, 4: { spaziergaenger: 2, wanderer: 3, familie: 4, radfahrer: 8 }, 5: { spaziergaenger: 2, wanderer: 2, familie: 3, radfahrer: 3, senioren: 4, busgruppe: 3, schulklasse: 3 }, 6: { spaziergaenger: 2, wanderer: 3, familie: 4, radfahrer: 3, senioren: 3, busgruppe: 3, schulklasse: 2, junge_gruppe: 3 }, 7: { wanderer: 2, familie: 3, senioren: 2, busgruppe: 2, junge_gruppe: 5, luxuspaar: 3 }, 8: { spaziergaenger: 2, wanderer: 3, familie: 4, radfahrer: 2, senioren: 3, busgruppe: 3, schulklasse: 2, junge_gruppe: 4, luxuspaar: 3 }, }; // ---------- Ereignisse & Ankündigungen (§13) ---------- // t in Sekunden. type: 'card' (Info/Ankündigung), 'decision' (A/B-Wahl). export const EVENTS = [ { id: 'hint_familien', wave: 2, type: 'card', icon: '📣', title: 'Tourismusverband meldet', text: 'Immer mehr Familien entdecken die Region als Ausflugsziel. ' + 'In etwa 5 Minuten werden deutlich mehr Familien erwartet.\n\n' + 'Fachlicher Hinweis: Familien achten auf kindgerechte Angebote, ' + 'kurze Wege und Sanitäranlagen.', }, { id: 'hint_radweg', wave: 3, type: 'card', icon: '🚴', title: 'Ankündigung: Radfernweg', text: 'Ein Radfernweg wird eröffnet – in 2 Phasen kommen deutlich mehr ' + 'Radfahrer durch den Ort.\n\nFachlicher Hinweis: Radfahrer sind schnell ' + 'unterwegs. Angebote wirken nur, wenn sie nah am Radweg liegen.', }, { id: 'decision_investor', wave: 4, type: 'decision', icon: '🏗️', title: 'Ein Investor will einsteigen', text: 'Ein Großinvestor bietet der Gemeinde 800 € Beteiligung – dafür will ' + 'er großflächig am Seeufer bauen. Der Eingriff würde die Natur belasten ' + '(Umwelt −10).\n\nRaumplanung heißt abwägen: kurzfristiges Geld gegen ' + 'langfristige Qualität.', yes: { label: 'Angebot annehmen (+800 €, Umwelt −10)', apply: s => { s.budget += 800; s.env = Math.max(5, s.env - 10); } }, no: { label: 'Ablehnen (Umwelt +3)', apply: s => { s.env = Math.min(100, s.env + 3); } }, }, { id: 'hint_bus', wave: 5, type: 'card', icon: '🚌', title: 'Reiseveranstalter plant Tagesfahrten', text: 'Ein Reiseveranstalter nimmt die Region ins Programm: Ab jetzt ' + 'kommen Busreisegruppen und Schulklassen.\n\nFachlicher Hinweis: ' + 'Busgruppen brauchen eine Parkmöglichkeit – sonst reisen sie ' + 'verärgert wieder ab. WC und Gasthaus nicht vergessen.', }, { id: 'hint_winter', wave: 6, type: 'card', icon: '❄️', title: 'Der Winter naht', text: 'Schneesicherheit ist im Tal unsicher, aber höher gelegene Bereiche ' + 'bleiben attraktiv. Ein Lift würde den Hang erschließen und oben neue ' + 'Bauplätze schaffen.\n\nFachlicher Hinweis: Ein Lift ist teuer und ' + 'belastet die Umwelt – ganzjährige Nutzung (Wandern, Aussicht) ' + 'verbessert die Rechnung.', }, { id: 'decision_dorffest', wave: 6, type: 'decision', icon: '🎪', title: 'Dorffest am Wochenende?', text: 'Der Verein möchte ein Dorffest veranstalten. Kosten: 150 €. ' + 'Dafür steigt die Stimmung: Alle Gäste der nächsten Zeit sind ' + 'spürbar zufriedener.', yes: { label: 'Fest finanzieren (−150 €)', can: s => s.budget >= 150, apply: s => { s.budget -= 150; s.festUntil = s.time + 120; }, }, no: { label: 'Diesmal nicht', apply: () => {} }, }, { id: 'hint_luxus', wave: 7, type: 'card', icon: '💎', title: 'Anspruchsvolle Gäste im Anmarsch', text: 'Die Region wird bekannter: Junge Reisegruppen suchen Action am ' + 'Berg, wohlhabende Paare erwarten Qualität – gutes Restaurant ' + '(Wirtshaus Stufe 4+), Wellness, Kultur und eine intakte Landschaft.', }, { id: 'hint_endphase', wave: 8, type: 'card', icon: '🏁', title: 'Endphase', text: 'Die letzten 2:30 Minuten! Starke Besucherströme kommen. Jetzt ' + 'zählt: Läuft deine Region rund – für alle Gruppen, wirtschaftlich ' + 'und ökologisch?', }, ]; // ---------- Social-Media-Feed (§8.3) ---------- export const FEED_TEXTS = { 5: [ '{best} war perfekt für unseren Ausflug – gerne wieder!', 'Traumhafter Tag! Besonders {best} hat uns begeistert.', 'Fünf Sterne! {best} und die Landschaft – ein Traum.', ], 4: [ 'Sehr schöner Ausflug, {best} hat uns gut gefallen.', 'Fast perfekt – {best} war toll, ein bisschen mehr Auswahl wäre schön.', ], 3: [ 'Schöne Landschaft, aber {missing} haben wir vermisst.', 'Ganz nett. {best} war okay, aber es fehlt noch etwas – z. B. {missing}.', ], 2: [ 'Für uns gab es leider zu wenig zu tun – {missing} würde helfen.', 'Enttäuschend wenig Angebot. {missing} fehlt.', ], 1: [ 'Nie wieder. Es gab praktisch nichts für uns – {missing} fehlt völlig.', 'Schade um die schöne Gegend – ohne Angebote fahren wir woanders hin.', ], traffic: 'Schöne Gegend, aber der Verkehr im Ort war wirklich mühsam.', env: 'Die vielen Eingriffe in die Natur haben uns den Ausflug verleidet.', noParking: 'Unser Bus fand keinen Parkplatz – wir mussten weiterfahren!', }; // ---------- Regionsprofile (§12.3) ---------- export const PROFILES = [ { id: 'krisenregion', name: 'Krisenregion', icon: '🌧️', check: s => s.result.avgStars < 2.5 || s.budget < -1000 || s.env < 35, text: 'Schlechte Bewertungen, Geldsorgen oder Umweltprobleme: Die Region hat ihre Richtung noch nicht gefunden.', }, { id: 'nachhaltig', name: 'Nachhaltige Modellregion', icon: '🌍', check: s => s.result.avgStars >= 3.5 && s.env >= 65, text: 'Hohe Zufriedenheit UND intakte Umwelt – so sieht zukunftsfähiger Tourismus aus.', }, { id: 'massentourismus', name: 'Massentourismusregion', icon: '🏗️', check: s => s.revenueTotal >= 3000 && s.env < 50, text: 'Wirtschaftlich stark, aber auf Kosten der Natur – wie lange geht das gut?', }, { id: 'familienregion', name: 'Familienregion', icon: '👨‍👩‍👧‍👦', check: s => (s.stats.groups.familie?.count ?? 0) >= 8 && (s.stats.groups.familie?.avg ?? 0) >= 3.8, text: 'Familien fühlen sich hier besonders wohl – kindgerechte Angebote zahlen sich aus.', }, { id: 'aktivregion', name: 'Aktivregion', icon: '🚵', check: s => avgOf(s, ['radfahrer', 'wanderer', 'junge_gruppe']) >= 3.8, text: 'Sport, Berg und Bewegung: Aktive Gäste lieben deine Region.', }, { id: 'kulturregion', name: 'Kulturregion', icon: '🏛️', check: s => s.buildings.some(b => b.type === 'museum') && avgOf(s, ['senioren', 'schulklasse', 'busgruppe']) >= 3.8, text: 'Kultur und Dorfleben ziehen Senioren, Schulklassen und Gruppen an.', }, { id: 'entwicklung', name: 'Ausflugsregion in Entwicklung', icon: '🌱', check: () => true, text: 'Eine solide Basis ist gelegt – die klare Spezialisierung fehlt noch.', }, ]; function avgOf(s, ids) { let sum = 0, n = 0; for (const id of ids) { const g = s.stats.groups[id]; if (g?.count) { sum += g.stars; n += g.count; } } return n ? sum / n : 0; } // ---------- Synergien (§9.2 synergyRules) ---------- // Stehen zwei passende Gebäude nah beieinander (Pixel-Radius), verstärken sie // sich gegenseitig. bonus = zusätzlicher Wirkungsfaktor (+X %). Beidseitig. export const SYNERGIES = [ { a: 'skilift', b: 'almhuette', radius: 260, bonus: 0.25, why: 'Almhütte + Lift: Einkehr direkt an der Bergstation' }, { a: 'aussicht', b: 'naturschutz', radius: 200, bonus: 0.20, why: 'Aussicht + Naturschutz: unverbaute Landschaft' }, { a: 'cafe', b: 'marktstand', radius: 160, bonus: 0.18, why: 'Café + Marktstand: gemütlicher Bummel' }, { a: 'wirtshaus', b: 'souvenir', radius: 170, bonus: 0.15, why: 'Wirtshaus + Souvenirshop: Einkehr & Andenken' }, { a: 'spielplatz', b: 'eisdiele', radius: 160, bonus: 0.22, why: 'Spielplatz + Eisdiele: Familienglück' }, { a: 'badesteg', b: 'eisdiele', radius: 190, bonus: 0.18, why: 'Badesteg + Eisdiele: Sommer am See' }, { a: 'museum', b: 'cafe', radius: 170, bonus: 0.15, why: 'Museum + Café: Kultur mit Pause' }, { a: 'radstation', b: 'cafe', radius: 170, bonus: 0.15, why: 'Radstation + Café: Boxenstopp für Radler' }, { a: 'skilift', b: 'aussicht', radius: 240, bonus: 0.18, why: 'Lift + Aussicht: Panorama vom Gipfel' }, { a: 'wellness', b: 'aussicht', radius: 200, bonus: 0.15, why: 'Wellness + Aussicht: Erholung mit Weitblick' }, ]; // Ziele fürs Ziele-Panel (§12.1) export const GOALS = [ { id: 'rating', label: 'Ø Bewertung ≥ 3,5 Sterne', check: s => currentAvg(s) >= 3.5 }, { id: 'revenue', label: 'Wertschöpfung ≥ 2500 €', check: s => s.revenueTotal >= 2500 }, { id: 'env', label: 'Umweltwert ≥ 45', check: s => s.env >= 45 }, { id: 'debt', label: 'Nicht unter −1000 € rutschen', check: s => s.budget > -1000 }, ]; export function currentAvg(s) { const g = s.stats.all; return g.count ? g.stars / g.count : 0; }