Tourismustal: iPad-Test-Feedback Runde 1

- Bikepark: echtes 2x2-Footprint (canPlace/place/demolish/upgrade/Report
  ankerbasiert, buildingRef auf Teilflaechen), Sprite ohne Schatten neu
  generiert, zentriert gross gezeichnet. Baukriterium gelockert: Bahn-Naehe
  ersetzt die Zonen-Regel (wie Hotel im Bahn-Umkreis).
- Zeit halbiert: 1 Tag = 4 s bei 1x (Absturz-Rate entsprechend angepasst).
- Problem-Sprechblasen alle ~3 min am Gebaeude (Personal gesucht / kein
  Schnee), erster Hinweis nach 15-35 s.
- Heli: sichtbares Abheben/Landen am Pad (aus vorigem Commit) bleibt.
- Test-Harnesse (model-test, strategien) wandern in App/sims/tourismustal/tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 09:20:06 +02:00
parent 2b4a92e614
commit 16c562ed1d
7 changed files with 276 additions and 22 deletions
@@ -0,0 +1,76 @@
// Modell-Langzeittest ohne Browser: spielt eine typische Schüler-Strategie
// über 600 Tage (5 Jahre) durch inkl. Freischalt-Reihenfolge und prüft,
// ob Balancing und Meilensteine plausibel bleiben.
import { createState, simulateDay, canPlace, place, MAP_COLS, MAP_ROWS } from '../js/model.js';
import { MILESTONES } from '../js/data.js';
const s = createState();
// Bau-Wunschliste in sinnvoller Reihenfolge; gebaut wird, sobald
// freigeschaltet, leistbar und ein Platz gefunden ist.
const queue = ['restaurant', 'lift', 'access', 'hotel', 'staffhouse',
'restaurant', 'lift', 'snow'];
function tryBuildNext() {
const type = queue[0];
if (!type) return;
for (let r = MAP_ROWS - 1; r >= 0; r--) {
for (let c = 0; c < MAP_COLS; c++) {
if (canPlace(s, type, c, r).ok) {
place(s, type, c, r);
queue.shift();
console.log(` Tag ${s.day + 1}: ${type} gebaut @ ${c},${r}`);
return;
}
}
}
}
let goalsDay = null;
const milestonesFired = new Set();
for (let d = 1; d <= 600; d++) {
// Meilensteine wie im Spiel freischalten
for (const m of MILESTONES) {
if (!milestonesFired.has(m.id) && m.when(s)) {
milestonesFired.add(m.id);
s.unlocked.add(m.unlock);
console.log(` Tag ${d}: Meilenstein ${m.id}${m.unlock} frei`);
}
}
tryBuildNext();
simulateDay(s);
if (s.goalsDone && goalsDay === null) goalsDay = d;
if (!Number.isFinite(s.money) || !Number.isFinite(s.nature)) {
console.error('NaN am Tag', d); process.exit(1);
}
if (d % 120 === 0) {
console.log(`Tag ${d} (${s.season} ${s.year}): Geld ${s.money} € · Gäste ${s.guestsToday} · maxGäste ${s.stats.maxGuests} · Zufr. ${s.satisfaction} % · Natur ${Math.round(s.nature)} %`);
}
}
console.log('---');
console.log('Nicht gebaut:', queue.length ? queue : 'alles gebaut');
console.log('Meilensteine:', [...milestonesFired].join(', ') || 'KEINE');
console.log('Ziele erreicht an Tag:', goalsDay ?? 'NICHT erreicht');
console.log('Endstand:', `${s.money}`, `· maxGäste ${s.stats.maxGuests}`, `· Natur ${Math.round(s.nature)} %`, `· Zufriedenheit ${s.satisfaction} %`);
// --- Kurzcheck der weiteren Szenen: Vorbauten + 240 Tage ohne Ausreißer ---
for (const scene of ['boom', 'klimawandel']) {
const st = createState(scene);
console.log(`\n[${scene}] Vorbauten:`, st.buildings.map(b => b.type).join(', '),
`· Start: ${st.money} € · Natur ${st.nature} % · freigeschaltet: ${[...st.unlocked].join(', ')}`);
// im Klimawandel-Szenario einen Panoramaweg dazubauen
if (scene === 'klimawandel') {
outer:
for (let r = MAP_ROWS - 1; r >= 0; r--) {
for (let c = 0; c < MAP_COLS; c++) {
if (canPlace(st, 'trail', c, r).ok) { place(st, 'trail', c, r); break outer; }
}
}
}
for (let d = 1; d <= 240; d++) {
simulateDay(st);
if (!Number.isFinite(st.money) || !Number.isFinite(st.nature)) {
console.error(`[${scene}] NaN am Tag`, d); process.exit(1);
}
}
console.log(`[${scene}] Tag 240 (${st.season} ${st.year}): Geld ${st.money} € · maxGäste ${st.stats.maxGuests} · maxSommer ${st.stats.maxSummerGuests} · Natur ${Math.round(st.nature)} % · Zufr. ${st.satisfaction} %`);
}