2026-09-01 · javascript · canvas · svelte · spiel
Lunar Lander: der Autopilot ist ein Thermostat
Das Landespiel von 1969 als Canvas-Nachbau mit Mondgravitation, drei Düsen und einem Autopiloten, der nur ein Proportionalregler ist.
Eine Fähre sinkt auf eine Strichzeichnung vom Mond, drei Düsen halten dagegen, der Treibstoff schwindet. Lunar Lander entstand 1969 als Textspiel eines Schülers auf der PDP-8, zehn Jahre bevor Atari daraus Arcade-Vektorgrafik machte. Hier fliegt es im Canvas: Der Roboter sitzt in der Kuppel, der Hog weist am Landeplatz mit zwei Kellen ein, und wahlweise übernimmst du die Düsen selbst.
Bereit. Starte den Autopiloten oder flieg selbst.
Die Demo braucht JavaScript, einen Server gibt es nicht. Bewegung entsteht erst nach einem Klick; wer nichts startet, sieht ein stilles Standbild mit Instrumenten.
Fallen in vier Zeilen
Die Physik ist derselbe Euler-Schritt wie in der Schneeballschlacht aus dem letzten Beitrag, nur andersherum erzählt: Dort fliegt ein Ball, den nichts mehr rettet, hier hält eine Fähre mit drei Düsen gegen die Gravitation.
lander.vy += G; // der Mond zieht
if (duesen.hoch) { lander.vy -= SCHUB_HOCH; treibstoff -= 0.3; }
if (duesen.rechts) { lander.vx += SCHUB_SEITE; treibstoff -= 0.1; }
if (duesen.links) { lander.vx -= SCHUB_SEITE; treibstoff -= 0.1; }
Der Treibstoff ist die eigentliche Spielregel. Ohne ihn wäre die Hauptdüse ein Schwebe-Knopf und die Landung eine Formalität; mit ihm wird jeder Korrekturstoß eine Ausgabe, die am Ende fehlen kann. Ist der Tank leer, feuern die Düsen nicht mehr, und der Rest ist freier Fall. Die Schleife läuft dabei mit festen 60 Schritten pro Sekunde, entkoppelt vom Display: Auf einem 120-Hertz-Monitor wäre das Spiel sonst doppelt so schnell.
Der Autopilot ist ein Thermostat
Der Anspruch klingt nach Raumfahrt, der Code nach Heizungskeller. Ein Thermostat kennt eine Solltemperatur und heizt, wenn die Ist-Temperatur darunter liegt. Der Autopilot macht dasselbe mit der Sinkrate: Die Soll-Sinkrate wächst mit der Resthöhe, und die Hauptdüse feuert genau dann, wenn real schneller gesunken wird als erlaubt.
Die erste Fassung kannte als Zielhöhe nur das Pad. Sie flog damit in Serie in den Hang: brav sinkend Richtung Pad-Niveau, während unter ihr das Gebirge anstieg, bis sie mit vorbildlicher Sinkrate mitten am Hügel aufsetzte. Ein Regler regelt exakt den Sollwert, den man ihm gibt. Die Korrektur ist deshalb kein Pfadplan, sondern ein zweiter Sollwert:
const ueberPad = lander.x >= PAD.x1 && lander.x <= PAD.x2;
const zielY = ueberPad ? PAD.y : REISEHOEHE;
const hoehe = zielY - (lander.y + 16);
const sollVy = ueberPad
? Math.min(1.8, Math.max(0.3, hoehe * 0.02))
: Math.min(1.8, Math.max(-0.8, hoehe * 0.02));
duesen.hoch = lander.vy > sollVy;
Links vom Pad hält die Fähre Reiseflughöhe überm Gebirge und darf dafür auch steigen. Über dem Pad sinkt sie mit mindestens 0,3, sonst nähert sich der Endanflug exponentiell und schwebt, bis der Tank leer ist. Seitwärts läuft dieselbe Rechnung noch einmal mit der Drift Richtung Pad-Mitte. Nach der Wurf-KI der Schneeballschlacht ist das der nächste Fall, in dem eine simple Rückkopplung wie Absicht aussieht: Die Fähre bremst „vorausschauend” ab, dabei reagiert sie nur.
Das Urteil sind zwei Vergleiche
Ob die Landung gelingt, entscheidet der kleinste Teil des Programms:
const aufPad = lander.x - LANDER_HALB >= PAD.x1 && lander.x + LANDER_HALB <= PAD.x2;
const sanft = Math.abs(lander.vy) <= MAX_VY && Math.abs(lander.vx) <= MAX_VX;
Beide Füße auf dem Pad, senkrecht wie seitlich langsam genug: gelandet. Alles andere ist ein Bruch, und die Trümmer kullern den Hang hinunter. Der Hog am Landeplatz hebt oder senkt dazu die Kellen. Dass ausgerechnet das Urteil aus zwei Vergleichen besteht, während Physik und Regler das Programm füllen, ist eine hübsche Miniatur unserer Arbeitsteilung: Die Maschine fliegt, aber was als gelungen gilt, steht woanders und ist kurz.
