Tourismustal: iPad-Feedback Runde 2 (5 Punkte)

- Bau-Ablehnung erklaert jetzt immer auch die Bauregeln des Gebaeudes
  (Toast: Grund + Beschreibung, 7 s).
- Lehrer-Schalter fuer den Vorlese-Modus: class_modules.audio_info
  (Migration, Default AN) + modules-API set_audio_info + Toggle auf der
  Modul-Karte im Cockpit (Vorlesen an/aus) + Wrapper injiziert
  TT_FEATURES.audioInfo + Sim blendet den Umschalter aus und erzwingt
  Lese-Modus, wenn gesperrt.
- Bus zeigt bei Rueckfahrt sein Heck: Heckscheibe + rote Ruecklichter,
  Tuer/Spiegel richtungsabhaengig.
- Bikepark-Flowtrails: 3 Stufen hangAUFwaerts (gleiche Richtung wie
  Lift-Pisten).
- Info-Button an jedem Bauwerk im Baumenue: erklaert Wirkung, Kosten,
  Lage-Regeln und Lehrplan-Wissen VOR dem Bau.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 18:07:03 +02:00
parent cb60a8c16d
commit 55a83d5daf
10 changed files with 131 additions and 35 deletions
+40 -24
View File
@@ -1392,9 +1392,11 @@ export class IsoRenderer {
const s = this.state;
if (!s.buildings.some(b => b.type === 'access')) return;
const ctx = this.ctx;
let f = (this.simTime / 26000) % 1;
const phase = (this.simTime / 26000) % 1;
let f = phase;
if (f > 0.5) f = 1 - f;
f *= 2;
const dir = phase <= 0.5 ? 1 : -1; // 1 = fährt nach rechts unten, 1 = zurück
const c = CORE.c0 + 0.5 + f * (CORE.c1 - CORE.c0 - 1);
const r = ROAD_ROW;
const tile = s.map[r][Math.min(MAP_COLS - 1, Math.round(c))];
@@ -1432,26 +1434,39 @@ export class IsoRenderer {
ctx.lineTo(...P(b, W, winT)); ctx.lineTo(...P(a, W, winT));
ctx.closePath(); ctx.fill();
}
// Türlinie
// Türlinie (Tür sitzt Richtung Front)
ctx.strokeStyle = this._shade(body, -34); ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(...P(L * 0.06, W)); ctx.lineTo(...P(L * 0.06, W, winB)); ctx.stroke();
ctx.beginPath(); ctx.moveTo(...P(L * 0.06 * dir, W)); ctx.lineTo(...P(L * 0.06 * dir, W, winB)); ctx.stroke();
// Front (Fahrtrichtung, +L)
// Sichtbares Ende (+L): je nach Fahrtrichtung FRONT oder HECK
ctx.fillStyle = body;
ctx.beginPath();
ctx.moveTo(...P(L, W)); ctx.lineTo(...P(L, -W));
ctx.lineTo(...P(L, -W, H)); ctx.lineTo(...P(L, W, H));
ctx.closePath(); ctx.fill();
// Windschutzscheibe
ctx.fillStyle = 'rgba(223,232,236,0.92)';
ctx.beginPath();
ctx.moveTo(...P(L, W * 0.7, winB)); ctx.lineTo(...P(L, -W * 0.7, winB));
ctx.lineTo(...P(L, -W * 0.7, winT)); ctx.lineTo(...P(L, W * 0.7, winT));
ctx.closePath(); ctx.fill();
// Scheinwerfer
ctx.fillStyle = '#ffe6a0';
const hl1 = P(L, W * 0.6, H * 0.3), hl2 = P(L, -W * 0.6, H * 0.3);
ctx.beginPath(); ctx.arc(hl1[0], hl1[1], 1.1, 0, 7); ctx.arc(hl2[0], hl2[1], 1.1, 0, 7); ctx.fill();
if (dir === 1) {
// Windschutzscheibe
ctx.fillStyle = 'rgba(223,232,236,0.92)';
ctx.beginPath();
ctx.moveTo(...P(L, W * 0.7, winB)); ctx.lineTo(...P(L, -W * 0.7, winB));
ctx.lineTo(...P(L, -W * 0.7, winT)); ctx.lineTo(...P(L, W * 0.7, winT));
ctx.closePath(); ctx.fill();
// Scheinwerfer
ctx.fillStyle = '#ffe6a0';
const hl1 = P(L, W * 0.6, H * 0.3), hl2 = P(L, -W * 0.6, H * 0.3);
ctx.beginPath(); ctx.arc(hl1[0], hl1[1], 1.1, 0, 7); ctx.arc(hl2[0], hl2[1], 1.1, 0, 7); ctx.fill();
} else {
// Heckscheibe (kleiner, höher gesetzt)
ctx.fillStyle = 'rgba(223,232,236,0.85)';
ctx.beginPath();
ctx.moveTo(...P(L, W * 0.55, winB + 1.5)); ctx.lineTo(...P(L, -W * 0.55, winB + 1.5));
ctx.lineTo(...P(L, -W * 0.55, winT)); ctx.lineTo(...P(L, W * 0.55, winT));
ctx.closePath(); ctx.fill();
// rote Rücklichter
ctx.fillStyle = '#c0392b';
const tl1 = P(L, W * 0.68, H * 0.3), tl2 = P(L, -W * 0.68, H * 0.3);
ctx.beginPath(); ctx.arc(tl1[0], tl1[1], 1.2, 0, 7); ctx.arc(tl2[0], tl2[1], 1.2, 0, 7); ctx.fill();
}
// Stoßstange
ctx.strokeStyle = '#2f3e46'; ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.moveTo(...P(L, W, H * 0.16)); ctx.lineTo(...P(L, -W, H * 0.16)); ctx.stroke();
@@ -1478,18 +1493,19 @@ export class IsoRenderer {
ctx.quadraticCurveTo(wp[0], wp[1] - 4.2, wp[0] + 3.7, wp[1] + 1);
ctx.stroke();
}
// Türgriff
// Türgriff (Richtung Front)
ctx.strokeStyle = this._shade(body, -42); ctx.lineWidth = 1;
const gh = P(L * 0.16, W, winB * 0.7);
const gh = P(L * 0.16 * dir, W, winB * 0.7);
ctx.beginPath(); ctx.moveTo(gh[0] - 2, gh[1]); ctx.lineTo(gh[0] + 2, gh[1]); ctx.stroke();
// Außenspiegel vorne
ctx.fillStyle = '#2f3e46';
const mir = P(L, W * 1.4, H * 0.62);
ctx.beginPath(); ctx.arc(mir[0], mir[1], 1.3, 0, 7); ctx.fill();
// Rücklicht hinten
ctx.fillStyle = '#c0392b';
const rl = P(-L, W, H * 0.3);
ctx.beginPath(); ctx.arc(rl[0], rl[1], 1.2, 0, 7); ctx.fill();
if (dir === 1) {
// Außenspiegel vorne + Rücklicht am hinteren (verdeckten) Ende
ctx.fillStyle = '#2f3e46';
const mir = P(L, W * 1.4, H * 0.62);
ctx.beginPath(); ctx.arc(mir[0], mir[1], 1.3, 0, 7); ctx.fill();
ctx.fillStyle = '#c0392b';
const rl = P(-L, W, H * 0.3);
ctx.beginPath(); ctx.arc(rl[0], rl[1], 1.2, 0, 7); ctx.fill();
}
ctx.lineWidth = 1;
}