# Design — Ereignisschleife und klassische Ereignis-Traps

## Context

Siehe proposal.md — Why. Technisch maßgeblich ist der Bestand aus Phase 3:

```
  crossterm          TerminalHost            RtState               Vm
  ---------          ------------            -------               --
  Key     --> next_event() --> Ereignis::Taste --> tasten[]   <-- INKEY$
  Resize  --> next_event() --> Ereignis::Groesse -> screen.resize()
  Ctrl+C  --> next_event() --> Ereignis::Abbruch -> abbruch
  Maus    --> x verworfen (`Ok(_) => {}` im TerminalHost)   <-- fehlt
  Zeit    --> x keine Quelle                                <-- fehlt

  Zustellpunkte heute: Instr::Stmt (screen dirty oder alle 1024 Ticks),
                       vor blockierender Eingabe.
```

Zwei Randbedingungen schneiden den Entwurf zu:

1. **Builtins sehen die VM nicht.** Ihre Signatur ist
   `(&mut RtState, &mut dyn Host, &mut [Value])`. `DOEVENTS` ist heute
   ein Builtin und kann deshalb prinzipiell keine Prozedur des laufenden
   Programms ausführen.
2. **`Vm::run()` ist nicht wiedereintrittsfähig.** Es gibt keinen
   Einstiegspunkt „führe Ziel X aus und komm zurück".

## Goals / Non-Goals

**Goals:**

- Eine Zustellung, die den unterbrochenen Zustand nicht verliert und
  ohne aktiven Trap nichts kostet.
- Zeitgesteuerte Programme im Korpus ohne echte Wartezeit und ohne
  Flackern zwischen zwei Läufen.
- Ein Ereignismodell, das die Forms-Ereignisse des nächsten Changes
  trägt, ohne dann umgebaut zu werden.

**Non-Goals:**

- Nebenläufigkeit. Es gibt weiterhin genau einen Ausführungsstrang; die
  Zeitquelle ist kein Thread, sondern eine Abfrage am Zustellpunkt.
- Priorisierbare oder verschachtelte Handler.

## Decisions

### D1 — Zustellung ist ein Vorgang der VM, kein Builtin

Die Prüfung „ist etwas fällig?" sitzt in der bestehenden Tick-Prüfung an
`Instr::Stmt`. Ist etwas fällig, springt die VM das Trap-Ziel an, als
hätte das Programm dort ein `GOSUB` ausgeführt. `DOEVENTS` und `SLEEP`
werden zu Anweisungen, die denselben Weg auslösen, statt zu Builtins,
die ihn nicht auslösen können.

| Alternative | Warum nicht |
|---|---|
| `&mut Vm` an alle Builtins durchreichen | Signaturänderung an ~150 Builtins; echte Rekursion in `run()` mit unbegrenzter Stapeltiefe |
| Trampolin: Builtin liefert „bitte Ziel X rufen" zurück | Trägt die klassischen Traps, aber modales `SHOW` des nächsten Changes wird zur Zustandsmaschine — der Umbau käme dann doch |

### D2 — Der Handler läuft in einem eigenen Frame, es wird nicht abgewickelt

`ON ERROR` wickelt die Frame-Kette bis zum Handler-Frame ab. Für einen
Trap ist das falsch: das Programm muss danach weiterlaufen. Die
Zustellung **legt** deshalb einen Frame auf den Modulrumpf **auf**, mit
einer Rückmarke auf die unterbrochene Stelle; `RETURN` nimmt ihn wieder
herunter.

```
   vor der Zustellung        waehrend des Handlers     nach RETURN
   ------------------        ---------------------     -----------
   [ SUB Rechne ]  <- top    [ Trap-Frame @Marke ]     [ SUB Rechne ]
   [ Modulrumpf  ]           [ SUB Rechne        ]     [ Modulrumpf ]
                             [ Modulrumpf        ]
```

Der Trap-Frame teilt die Modulvariablen (sie sind Globals), hat aber
einen eigenen `GOSUB`-Stapel — damit kann ein Handler selbst `GOSUB`
verwenden, ohne den unterbrochenen Stapel zu berühren.

### D3 — Die Zeit kommt vom Host

Der `Host`-Trait bekommt eine monotone Zeitabfrage (**BREAKING**). Die
Laufzeit rechnet daraus die Fälligkeit der Zeit-Traps; der Host weiß
nichts von Traps.

Grund ist die Prüfbarkeit: der `CaptureHost` stellt die Zeit selbst und
ein Korpusprogramm mit `ON TIMER(5)` läuft in Mikrosekunden und immer
gleich. Ein direkter Uhrzugriff in der Laufzeit hätte beides verhindert
und wäre zudem die zweite Uhr neben der von `TIMER`/`TIME$` — die
bleibt, wo sie ist, denn sie liefert Ortszeit, keine monotone Dauer.

### D4 — Maskierung als Tabelle, Prüfung über das vorhandene Flagwort

Je Quelle ein Zustand (`ON`/`OFF`/`STOP`) und höchstens ein anstehendes
Ereignis. Belegt aus der Original-Hilfe (`umfang-und-form.md`): der
Eintritt in den Handler führt selbsttätig `STOP` aus, das `RETURN`
selbsttätig `ON` — außer im Handler stand ein ausdrückliches `OFF`; und
`OFF` verwirft auch ein unter `STOP` gemerktes Ereignis. `EVENT` ist
keine dritte Zustandsebene, sondern eine Klammer mit nur `ON` und `OFF`. Die Tick-Prüfung bekommt ein weiteres Bit im vorhandenen
Flagwort neben `F_STEP`/`F_BREAK`/`F_POLL`. Ohne definierten Trap ist
das Bit aus, und die heiße Schleife prüft weiterhin genau ein Wort —
das trägt die Zusicherung „ohne aktiven Trap kein messbarer Aufwand"
aus der Spec.

### D5 — Getrappte Tasten werden erst am Zustellpunkt aussortiert

Die Tastenschlange bleibt, wie sie ist. Am Zustellpunkt vergleicht die
Laufzeit den Kopf der Schlange mit den aktiven `KEY`-Traps und nimmt die
Taste nur dann heraus. Damit wirkt ein `KEY(1) OFF` sofort und auch auf
Tasten, die schon in der Schlange liegen — Filtern beim Einsortieren
hätte den Zustand zum Zeitpunkt des Tastendrucks eingefroren.

### D6 — Virtuelle Zeit im Korpus über eine Kopfzeilen-Direktive

Der Harness kennt bereits `' tb-screen:` und `' tb-tz:`. Dazu kommt
`' tb-clock: <n>ms/Zustellpunkt`: die virtuelle Uhr des `CaptureHost`
rückt an jedem Zustellpunkt um `n` Millisekunden vor. Ein Programm mit
`ON TIMER(1)` und `' tb-clock: 100ms/Zustellpunkt` feuert damit
reproduzierbar nach zehn Zustellpunkten. Fehlt die Direktive und nutzt
das Programm Zeit-Traps, bricht der Harness ab, statt auf die Systemuhr
auszuweichen.

### D7 — Maus: Quelle jetzt, Auswertung mit den Steuerelementen

Die Maus kommt als Quelle in diesem Change herein, weil die
Eingangsaufgabe der Phase die Terminal-Events in die Warteschlange
führt. Zwei Festlegungen fallen dabei an:

- **Koordinaten in Zellen, 1-basiert.** `crossterm` zählt ab 0, der
  Bildschirm des Dialekts ab 1 (`LOCATE`, `CSRLIN`, `SCREEN(z, s)`).
  Umgerechnet wird an der Stelle, an der die Ereignisse entstehen — in
  der Darstellungsschicht —, damit es in der Laufzeit nur eine Zählweise
  gibt. Die Forms-Referenz nennt Mauskoordinaten SINGLE, aber in Zellen;
  die Umrechnung in Formularkoordinaten macht später das Objektmodell.
- **Ohne Verbraucher verworfen.** Bis die Steuerelemente da sind, nimmt
  niemand ein Mausereignis an. Es am Zustellpunkt zu verwerfen ist die
  einzige Variante, die keine unbegrenzt wachsende Warteschlange
  hinterlässt; Bewegungsereignisse kommen bei gedrückter Taste in hoher
  Frequenz.

### D8 — `SIGNAL` liegt auf Betriebssystemsignalen

Entscheidung des Projektinhabers vom 2026-09-04, Beleg und Begründung in
`umfang-und-form.md`. Abgebildet wird nur, was auf **allen**
Zielplattformen besteht:

| `n%` | Signal | Bemerkung |
|---|---|---|
| 1 | `SIGINT` | überall zustellbar |
| 2 | `SIGTERM` | unter Windows von außen nicht zustellbar (Abweichung) |

Der Signalhandler tut nichts weiter, als eine Marke zu setzen; der Host
liest sie beim nächsten Abholen und macht ein Ereignis daraus. Damit
läuft kein Programmcode im Signalkontext, und die Zustellung folgt
denselben Punkten wie jede andere Quelle. Liegen beide Marken an, hat
`SIGINT` Vorrang.

Ein Codepfad für alle drei Plattformen: `signal-hook` gilt sein
`flag`-Modul nicht nach Plattform ab und bildet unter Windows auf das
`signal` der C-Laufzeit ab. Die Abhängigkeit liegt ohnehin im Baum —
`crossterm` nutzt sie unter Unix für `SIGWINCH` —, sie wird für `tb-ui`
nur ausdrücklich deklariert, nicht neu hereingeholt. Damit entfällt der
sonst nötige zweite Pfad über den Konsolen-Steuerhandler von Windows.

### D9 — Umfang und Form aus den Einzelseiten der Original-Hilfe

Befund aus `phase-3-isam`: die Themenliste klärt den *Umfang*, die
Einzelseiten klären die *Form*. Für diesen Change heißt das: die
Wertebereiche von `ON KEY(n)`, die Reihenfolge gleichzeitig fälliger
Ereignisse und die Frage, ob Trapping in Prozeduren ruht, werden vor der
Implementierung aus den Einzelseiten belegt (erste Aufgabe in tasks.md),
nicht aus der Erinnerung nachgebaut. Ergibt die Hilfe nichts, gilt eine
dokumentierte Festlegung, die in der Sprachreferenz als Abweichung
steht.

### D10 — Schleifen bekommen eine Anweisungsgrenze im Rücksprung

Befund bei der Umsetzung von D1: der Codegenerator übersetzte eine
Schleife **ohne Rumpf** in reine Sprünge — in `FOR i = 1 TO 10000: NEXT`
gab es keine Anweisungsgrenze und damit keinen Zustellpunkt. Das trifft
alle vier Schleifenformen und betraf auch Breakpoints und den Abbruch,
war also schon vor diesem Change eine Lücke.

Der Rücksprung führt jetzt in jedem Fall über eine Grenze. Bei
`DO`/`WHILE` zeigt er auf die ohnehin vorhandene Grenze des
Schleifenkopfs — das kostet keine Instruktion. Nur `FOR` braucht eine
eigene, weil vor dem Schleifentest die Initialisierung steht.

## Risks / Trade-offs

- **Der Trap-Frame sieht einen halbfertigen Zustand** → Zustellung nur
  an Anweisungsgrenzen; die Liste der Zustellpunkte ist in der Spec
  abschließend und wird nicht „für den Komfort" erweitert.
- **Ereignisse feuern schneller, als der Handler zurückkehrt** →
  höchstens ein anstehendes Ereignis je Quelle, und die eigene Quelle
  ist während des Handlers gestoppt. Ein Zeit-Trap kann damit Fristen
  überspringen; das entspricht dem Vorbild.
- **Tick-Kosten** → `benches/vm.rs` misst vorher und nachher; die
  Zusicherung aus der Spec ist ein Messwert, keine Behauptung. Die
  zusätzliche Grenze je `FOR`-Durchlauf (D10) geht in dieselbe Messung
  ein.
- **`SIGNAL(2)` ist unter Windows wirkungslos** — es gibt dort kein
  `kill` → als Abweichung dokumentiert, statt ein Ersatzereignis zu
  erfinden, das es im Vorbild nicht gab.
- **`SIGINT` kollidiert mit dem Abbruch** → im Rohmodus liefert das
  Terminal Strg+C als Taste, nicht als Signal; ein von außen gesendetes
  `SIGINT` geht an den Trap, wenn einer aktiv ist, sonst bleibt es beim
  Abbruch. Dieselbe Regel wie bei getrappten Tasten.
- **`Host`-Trait ist BREAKING** → betrifft `TerminalHost`, `CaptureHost`
  und den Test-Host in `tb-cli`; alle drei liegen im Repo, kein externer
  Verbraucher.
- **Mausereignisse ohne Verbraucher könnten stumm falsch sein** — ein
  Fehler im Koordinatenmodell fiele erst im nächsten Change auf →
  Unit-Tests der Umrechnung an den Rändern (Zelle 1,1 und die letzte
  Zelle) und nach einer Größenänderung, nicht erst über Steuerelemente.

## Open Questions

- Ruht das Trapping in Prozeduren eines *anderen* Moduls? Mehrere Module
  gibt es erst mit der Projektverwaltung in Phase 5; die Antwort ändert
  hier weder Spec noch Aufgaben.
- Ob die Zeitquelle später auch Forms-`Timer`-Controls speist oder diese
  eine eigene Fälligkeitsliste bekommen, entscheidet der Change zum
  Objektmodell.
- Ob Mausbewegungen ohne gedrückte Taste zusammengefasst werden müssen
  (`MouseMove` kommt sehr häufig), zeigt sich erst mit dem ersten
  Verbraucher; das Ereignismodell bleibt davon unberührt.
