Files
Adminator 5f603cb193 Tourismusregion: Umwelt beißt jetzt + Schüler-Erklärung (Balance-Pass 1)
Diagnose per headless-Sim-Harness (.dontDeploy/tools/tr-balance-harness.mjs):
„alles zubauen" senkte die Umwelt nur 75→50 (über der 45-Schwelle) und der
Exit-Umwelt-Malus war TOTER Flag-Code → man konnte der Umwelt kaum schaden.

- model.js: (1) Exit-Umwelt-Malus wirklich angewandt — env<60 zieht bei
  umweltbewussten Gästen Zufriedenheit ab (je tiefer, desto mehr). (2) Über-
  tourismus-Drift im 10-s-Takt: dichte Bebauung (>6 Bauwerke) + Verkehr zehren
  die Umwelt aus. (3) Einmalige Lern-Warnkarte, wenn Umwelt < 50 kippt.
- data.js: Naturschutzgebiet-Beschreibung klar (rettet die Umwelt +20, Natur-
  Attraktionen +15 %, sperrt Bauplätze — Naturschutz vs. Ausbau abwägen).
- main.js: Willkommenskarte erklärt die Umwelt-Mechanik für Schüler.

Messung nachher: „alles zubauen" → Umwelt 75→37 (unter 45), Umweltziel
verfehlt, Ø-Sterne fallen. Feintuning Sterne-Kurve/Ansprüche-Eskalation
bleibt für Thomas' Playtest (Game-Feeling headless nicht bewertbar).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-19 18:10:42 +02:00

615 lines
29 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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: 'Hebt die Umwelt sofort um +20 und macht Natur-Attraktionen 15 % attraktiver. So rettest du die Landschaft, wenn zu viel Bauen die Umwelt gedrückt hat Wanderer und Naturfreunde danken es dir. Preis: sperrt umliegende Bauplätze (Naturschutz vor Ausbau).',
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;
}