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>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// Headless-Balance-Messung (temporär). node sim-harness.mjs
|
||||
import { createState, place, upgrade, callNextWave, tick, GOALS } from '../../App/sims/tourismusregion/js/model.js';
|
||||
import { BUILDINGS, GAME_LEN } from '../../App/sims/tourismusregion/js/data.js';
|
||||
|
||||
const ALL_TYPES = Object.keys(BUILDINGS);
|
||||
function upgradeAllMax(s) {
|
||||
for (const b of s.buildings) {
|
||||
const def = BUILDINGS[b.type];
|
||||
let guard = 0;
|
||||
while (b.level < def.cost.length && s.budget >= def.cost[b.level] && guard++ < 6) {
|
||||
const r = upgrade(s, b);
|
||||
if (!r || !r.ok) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run(strategy, mapId = 'talschleife') {
|
||||
const s = createState({ mapId, seed: 12345 });
|
||||
const dt = 0.5;
|
||||
let t = 0;
|
||||
|
||||
function tryBuildAll() {
|
||||
for (let i = 0; i < s.slots.length; i++) {
|
||||
const slot = s.slots[i];
|
||||
if (slot.building || slot.blocked) continue;
|
||||
for (const type of ALL_TYPES) {
|
||||
const def = BUILDINGS[type];
|
||||
if (!def || s.budget < def.cost[0]) continue;
|
||||
const r = place(s, type, i);
|
||||
if (r && r.ok) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function buildOneCafe() {
|
||||
// Minimal-Strategie: genau EIN Café (Thomas' „5-Sterne-Café macht alles grün")
|
||||
if (s.buildings.length) return;
|
||||
for (let i = 0; i < s.slots.length; i++) {
|
||||
if (s.slots[i].building || s.slots[i].blocked) continue;
|
||||
const r = place(s, 'cafe', i);
|
||||
if (r && r.ok) return;
|
||||
}
|
||||
}
|
||||
|
||||
let guard = 0;
|
||||
while (t < GAME_LEN && s.phase < 8 && guard++ < 2000) {
|
||||
if (strategy === 'buildAll' || strategy === 'maxedAll') tryBuildAll();
|
||||
if (strategy === 'maxedAll') upgradeAllMax(s);
|
||||
if (strategy === 'oneCafe') buildOneCafe();
|
||||
try { callNextWave(s); } catch (_) {}
|
||||
for (let k = 0; k < 40 && t < GAME_LEN; k++) { tick(s, dt); t += dt; }
|
||||
}
|
||||
if (strategy === 'maxedAll') upgradeAllMax(s);
|
||||
// Restzeit auslaufen lassen
|
||||
while (t < GAME_LEN && guard++ < 6000) { tick(s, dt); t += dt; }
|
||||
|
||||
const stars = s.stats.all.count ? s.stats.all.stars / s.stats.all.count : 0;
|
||||
return {
|
||||
strategy,
|
||||
env: Math.round(s.env),
|
||||
avgStars: +stars.toFixed(2),
|
||||
visitorsExited: s.stats.all.count,
|
||||
budget: Math.round(s.budget),
|
||||
buildings: s.buildings.length,
|
||||
traffic: Math.round(s.traffic || 0),
|
||||
goalsMet: GOALS.filter(g => g.check(s)).length + '/' + GOALS.length,
|
||||
};
|
||||
}
|
||||
|
||||
console.log('=== Tourismusregion Balance-Messung (talschleife) ===');
|
||||
for (const strat of ['maxedAll', 'buildAll', 'oneCafe']) {
|
||||
const r = run(strat);
|
||||
console.log(`${strat.padEnd(9)} → env ${String(r.env).padStart(3)} · Ø${r.avgStars}★ · ${r.buildings} Geb. · Verkehr ${r.traffic} · Ziele ${r.goalsMet} · Budget ${r.budget}`);
|
||||
}
|
||||
@@ -283,7 +283,7 @@ export const BUILDINGS = {
|
||||
},
|
||||
naturschutz: {
|
||||
name: 'Naturschutzgebiet', emoji: '🌿', cat: 'nature', unlockPhase: 4, maxLevel: 1, fx: '🦋', sound: 'pling',
|
||||
desc: 'Umwelt +20, Naturerlebnis +15 % – sperrt aber Bauplätze im Umkreis.',
|
||||
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],
|
||||
|
||||
@@ -112,6 +112,10 @@ function startGame() {
|
||||
+ 'See, Berg – und drei Wege, auf denen Gäste ankommen.\n\n'
|
||||
+ 'Ziel: zufriedene Gäste (Ø 3,5 ★), regionale Wertschöpfung (2500 €), '
|
||||
+ 'intakte Umwelt (≥ 45) und ein solides Budget.\n\n'
|
||||
+ '🌲 Die Umwelt ist deine Landschaft: Wanderer und Naturfreunde lieben sie intakt. '
|
||||
+ 'Zu viel Bauen und Verkehr lässt die Umwelt sinken – fällt sie zu tief, vergraulst '
|
||||
+ 'du genau diese Gäste. Ein 🌿 Naturschutzgebiet hebt die Umwelt wieder und zieht '
|
||||
+ 'Naturgäste an, sperrt aber Bauplätze in der Nähe. Abwägen ist alles!\n\n'
|
||||
+ 'Gebaut wird auf den markierten Bauplätzen am Weg – starte mit einem Wirtshaus!',
|
||||
});
|
||||
if (!localStorage.getItem('tourismusregion_seen')) {
|
||||
|
||||
@@ -119,6 +119,30 @@ export function tick(state, dt) {
|
||||
cost /= 6; // pro Minute → pro 10 s
|
||||
state.budget -= cost;
|
||||
state.opCostTotal += cost;
|
||||
|
||||
// Übertourismus (§10 Raumnutzungskonflikt): dichte Bebauung + viel Verkehr
|
||||
// zehren die Umwelt langsam aus. Ab ~6 fertigen Bauwerken beginnt der Druck.
|
||||
// „Alles zubauen" drückt die Umwelt so über die Partie klar unter die
|
||||
// 45er-Schwelle — vorher blieb sie fast konstant. Naturschutzgebiet (+20)
|
||||
// und maßvolles Bauen sind die Gegenmittel.
|
||||
const builtCount = state.buildings.reduce((n, b) => n + (b.construction <= 0 ? 1 : 0), 0);
|
||||
const overbuild = Math.max(0, builtCount - 6);
|
||||
const envErode = overbuild * 0.06 + state.traffic * 0.012; // pro 10 s
|
||||
if (envErode > 0) state.env = clampEnv(state.env - envErode);
|
||||
|
||||
// Einmalige Lern-Warnung, sobald die Umwelt kippt (didaktischer Hinweis).
|
||||
if (state.env < 50 && !state.envWarned) {
|
||||
state.envWarned = true;
|
||||
state.pendingCards.push({
|
||||
kind: 'card', icon: '🌲', title: 'Die Umwelt leidet!', sound: 'card',
|
||||
text: 'Deine Umwelt ist unter 50 gefallen. Umweltbewusste Gäste – '
|
||||
+ 'Wanderer, Naturfreunde, Radgruppen – werden unzufrieden, wenn sie '
|
||||
+ 'unter 45 sinkt, und bleiben irgendwann ganz weg.\n\n'
|
||||
+ 'Was hilft: maßvoller bauen, auf sanfte Anreise (Bus, Rad, Bahn) '
|
||||
+ 'setzen – oder ein 🌿 Naturschutzgebiet bauen, das die Umwelt sofort '
|
||||
+ 'um +20 hebt. Naturschutz gegen Ausbau abwägen ist der Kern des Spiels.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verkehr: Parkplätze + aktive Auto-/Busgruppen
|
||||
@@ -495,7 +519,14 @@ function exitVisitor(state, v) {
|
||||
const tPen = state.traffic * 0.08 * g.trafficSens;
|
||||
if (tPen > 4) malus.traffic = true;
|
||||
sat -= tPen;
|
||||
if (state.env < 45 && g.envSens >= 0.7) malus.env = true;
|
||||
// Umwelt-Malus WIRKLICH anwenden (war zuvor nur ein toter Flag ohne Wirkung):
|
||||
// eine kaputte Landschaft vergrault Gäste — je umweltbewusster, desto stärker,
|
||||
// und je tiefer die Umwelt fällt, desto heftiger. Ab env<60 spürbar, <40 hart.
|
||||
// Das ist der Raumnutzungskonflikt: zu viel zubauen → Umwelt kippt → Rating fällt.
|
||||
if (state.env < 60 && g.envSens >= 0.4) {
|
||||
sat -= g.envSens * (60 - state.env) * 0.5; // env 40 → ~6, env 20 → ~12 (× envSens)
|
||||
if (state.env < 45) malus.env = true;
|
||||
}
|
||||
if (v.timeIn > g.maxTime) { sat *= 0.92; malus.time = true; }
|
||||
|
||||
const ratio = Math.max(0, sat) / g.satTarget;
|
||||
|
||||
Reference in New Issue
Block a user