M1b: Brücke und Hülle #6

Merged
steven merged 21 commits from m1b-bruecke-und-huelle into main 2026-10-01 11:46:02 +00:00

21 Commits

Author SHA1 Message Date
Steven Wroblewski bc34bc61f7 Tests: Speichern beim Schließen und automatisches Speichern
Beides setzt Konzept 9 um („automatisch in festen Abständen“, Speichern
beim Schließen), und beides ließ sich entfernen, ohne dass ein Test
rot wurde.

- Schließen: Die Hauptszene bekommt NOTIFICATION_WM_CLOSE_REQUEST. Danach
  meldet ein SaveStore im selben Ordner einen Spielstand und genau eine
  Aufzeichnung.
- Automatisch: Die Hauptszene läuft per _run_tick bis zum Takt vor
  AUTOSAVE_TICKS, ohne Bilder abzuwarten. Bis dahin gibt es keinen
  Spielstand, einen Takt später gibt es einen.

Entfernt man das Speichern beim Schließen, schlägt der erste Test fehl;
entfernt man das automatische Speichern in _run_tick, der zweite. Ohne
das Schreiben der Aufzeichnung beim Schließen schlägt der erste ebenfalls
fehl.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:26:30 +02:00
Steven Wroblewski 5b76f828e0 CLAUDE.md: neue Zustandsfelder an allen Kodier-Stellen ergänzen
Die Regel sagte, wer ein Feld ergänze, tue das in snapshot.rs und prüfe
es in check_restorable. Das gilt nur für Felder von World. Spielerfelder
kodiert und dekodiert core/src/player.rs, das Inventar inventory.rs, die
Karte map.rs; deren Prüfungen stehen im jeweiligen Dekodierer, nicht in
check_restorable. M2 ergänzt Welt-, Feld- und Spielerzustand und folgt
diesem Satz.

Die Regel nennt jetzt alle vier Stellen und sagt, dass ein neues Feld
beim Dekodieren oder in check_restorable geprüft wird. Die Namen wurden
gegen den Code geprüft: encode_state und from_snapshot, Player::encode
und Player::decode, Inventory::encode und Inventory::decode sowie
Map::encode und Map::decode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:25:26 +02:00
Steven Wroblewski 0b20dd5459 Fragebogen M1b: harter Fels und Aufzeichnung nach einem Absturz
Frage 3 sagte, ein Fels brauche drei Schläge. Das gilt nur für weichen
Fels. Harter Fels (ab etwa 22 Feldern vom Start) lässt sich mit der
Startspitzhacke nicht abbauen; Schläge darauf bewirken nichts, und das
Feld bleibt grün umrandet. Ein Spieltester hielte das für einen Fehler.
Der Fragebogen sagt es jetzt vorher.

Frage 5 bat um die neueste Aufzeichnung. Eine Aufzeichnung entsteht aber
nur beim Schließen des Fensters oder beim Laden mit F9. Nach einem
Absturz oder nach der Meldung „Die Simulation wurde angehalten“ gibt es
keine Aufzeichnung dieser Sitzung, und die neueste Datei gehört zu einer
früheren. Dann soll der Tester stattdessen den Ordner logs schicken, und
zwar sofort, weil jeder Start ältere Logdateien verdrängt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:24:55 +02:00
Steven Wroblewski bd8533e189 main.gd: Kommentar zu dig_at richtigstellen, Autosave-Takt ableiten
Der Kommentar an dig_at sagte, der Kern prüfe alles außer der
Abklingzeit. Das stimmt nicht: Der Kern prüft Abklingzeit, Feld,
Gelände, Werkzeugstufe und freien Platz, aber nicht die Reichweite. Die
prüft nur die Hülle (in _update_marker und _try_dig), weil die Figuren
außerhalb des Gleichschritts laufen und der Kern ihren Ort nicht kennt.
Der Plan für M1c darf sich nicht darauf verlassen. Der Kommentar nennt
jetzt, was der Aufrufer und was der Kern prüft.

AUTOSAVE_TICKS wiederholte mit 2400 die Taktrate von 20 pro Sekunde. Die
Konstante wird jetzt als zwei Minuten aus GwSession.TICKS_PER_SECOND
abgeleitet; GDScript nimmt das in einer const an und rechnet 2400.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:23:45 +02:00
Steven Wroblewski dff213cc61 Testdateien: Löschen nur unter user://tests, Altlasten räumen, Wortlaut
user://tests liegt im echten Benutzerordner des Spiels, neben Spielstand,
Schlüssel und Aufzeichnungen des Spielers. TestFiles.remove_dir löschte
jeden Pfad, den es bekam; ein falsches Argument hätte echte Spielerdateien
gelöscht. Tests, die früh mit einem Laufzeitfehler abbrachen, ließen ihren
Ordner zurück (es lagen 15 solche Ordner im Benutzerordner).

- remove_dir vereinfacht den Pfad (simplify_path) und verweigert alles,
  was nicht user://tests oder darunter liegt, mit push_error und ohne zu
  löschen. Die Wurzel steht in der Konstante TestFiles.ROOT, die auch
  temporary_dir benutzt.
- run_tests.gd entfernt user://tests zu Beginn von _run_all, vor dem
  Fehlerzähler, aber nur wenn der Ordner existiert, weil Godot beim
  Auflisten eines fehlenden Ordners einen Fehler meldet.
- CLAUDE.md sagt jetzt, dass die Tests unter user://tests schreiben und
  nie die echten Dateien des Spielers anfassen.

Das Verweigern wurde einmal von Hand geprüft, mit einem Wegwerfskript auf
selbst angelegten Ordnern: Ohne Schutz löschte remove_dir sie, mit Schutz
bleiben sie samt Datei stehen und es erscheint ein Fehler, auch für einen
Geschwisterordner user://tests_probe und für user://tests/../…. Danach
waren die 15 Altlasten verschwunden und die Ausgabe fehlerfrei, auch ohne
vorhandenen Ordner user://tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:23:01 +02:00
Steven Wroblewski 400556134f Testläufer meldet einen Fehler, wenn kein Test lief
quit(1 if failed > 0 else 0) beendete einen Lauf mit "0 passed, 0 failed"
mit Code 0, zum Beispiel nachdem der Testordner umbenannt wurde oder alle
Tests falsch benannt waren. Die Prüfliste sah dann grün aus, ohne etwas
geprüft zu haben.

Der Läufer endet jetzt auch mit Code 1, wenn kein Test bestanden wurde.
Der Doc-Kommentar sagt das. Einmal von Hand geprüft: Mit einem Dateifilter,
den keine Testdatei erfüllt, meldete der alte Läufer 0 passed, 0 failed
mit Code 0, der neue endet mit Code 1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:21:31 +02:00
Steven Wroblewski d986f2a5db Test: Startzeitpunkt einer dekodierten Aufzeichnung ist festgeschrieben
Recording::from_bytes leitet start_tick aus der gespeicherten Startwelt
ab. Kein Test prüfte das: Setzte ein Fehler start_tick beim Dekodieren
auf 0, blieb er unbemerkt, und eine so dekodierte Aufzeichnung würde
beim erneuten Kodieren oder Verlängern abstürzen.

Der Test a_recording_can_start_in_the_middle_of_a_session kodiert die
Aufzeichnung jetzt, dekodiert sie wieder und prüft, dass sie beim
selben Takt endet und der ursprünglichen gleicht. Mit start_tick = 0
beim Dekodieren schlägt er fehl (Endtakt 70 statt 120).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:20:22 +02:00
Steven Wroblewski 8aaeae5cf7 Test: volle Spielerliste mit vielen Offline-Spielern wird geladen
Eine Welt kennt bis zu 16 Spieler, von denen höchstens vier online sind.
Kein Test lud eine Welt mit mehr als vier bekannten Spielern; ein Fehler,
der in check_restorable alle bekannten statt der Online-Spieler zählt,
hätte gültige Spielstände zurückgewiesen und blieb unbemerkt. Mit dem
Koop-Spiel in M1c sind mehr als vier bekannte Spieler der Normalfall.

Der neue Test baut die Welt über Befehle auf: 16 Schlüssel treten in
Vierergruppen bei, jede Gruppe außer der letzten geht wieder, bevor die
nächste kommt. from_snapshot muss dieselbe Welt liefern, load alle Spieler
offline. Mit dem Fehler (alle Spieler gezählt) schlägt der Test mit
Invalid("online players") fehl.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:19:23 +02:00
Steven Wroblewski fc8d5cb164 Textwächter erkennt gruppierte std-Importe und globale Sperren
Mit `use std::{fs, process};` und anschließendem `fs::remove_file(…)`
kam bisher beides durch: Der Textwächter suchte `std::fs` wörtlich, und
Clippy kennt nur die aufgelisteten Funktionen. Globaler veränderlicher
Zustand über `static` mit Mutex, RwLock, OnceLock oder LazyLock wurde
auch nicht erkannt.

Neu in FORBIDDEN: `std::{` (gruppierte Importe verbergen dem Text die
Modulnamen) sowie Mutex, RwLock, OnceLock und LazyLock. Der Kern in
core/src nutzt nichts davon. Der Kommentar in core/clippy.toml behauptet
jetzt nur noch, was Clippy leistet, und CLAUDE.md nennt die Regel, dass
jeder std-Pfad in einer eigenen use-Zeile steht.

Der bestehende Selbsttest every_forbidden_term_is_detected deckt die
neuen Einträge ab.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:18:26 +02:00
Steven Wroblewski 842213c574 Spielstand bleibt erhalten, wenn das letzte Umbenennen scheitert
SaveStore.write_save verschob die geprüfte Temporärdatei direkt auf
world.grw. Godot löscht unter Windows ein vorhandenes Ziel, bevor es die
Datei verschiebt. Scheiterte die Verschiebung, zum Beispiel weil ein
Virenscanner world.grw.tmp festhielt, war der vorige Spielstand schon weg.

Jetzt wird der vorige Stand zuerst nach world.grw.bak verschoben, dann
rückt die Temporärdatei nach. Scheitert das, kommt der Stand aus der
Sicherung zurück; nach Erfolg wird die Sicherung gelöscht. Bleibt nach
einem Absturz zwischen den beiden Verschiebungen nur world.grw.bak übrig,
holt SaveStore sie beim Öffnen des Ordners zurück.

Neue Tests: ein offenes Handle auf world.grw.tmp, eine blockierte
Sicherung, die Wiederherstellung aus der Sicherung und dass eine
Sicherung einen vorhandenen Stand nie ersetzt.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 13:16:02 +02:00
Steven Wroblewski 68d063209c Startskript, Regeln und Probelauf für M1b
play.bat baut die Brücke und startet das Spiel. CLAUDE.md beschreibt
jetzt Kern, Brücke und Hülle mit allen Prüfbefehlen, die Versionsregel
für festgeschriebene Werte und die Zuschreibung in Commits. Ein
Fragebogen begleitet den ersten Probelauf.

Der erste Godot-Import in einem frischen Checkout stürzt beim Beenden
ab; play.bat wiederholt ihn deshalb einmal.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 12:26:28 +02:00
Steven Wroblewski 754d7f1e92 Speichern meldet Fehler und schützt unbrauchbare Stände
Lässt sich ein unbrauchbarer Spielstand nicht beiseitelegen, bleibt das
Speichern gesperrt, bis ein Stand erfolgreich geladen wird; so wird er
nie überschrieben. Ein gescheitertes automatisches Speichern zeigt
SAVE_FAILED, und jedes gescheiterte Speichern wird protokolliert.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 12:15:05 +02:00
Steven Wroblewski 68483c648e Darstellung und Hauptszene
Die Hauptszene verbindet Kern, Karte, Figur, Anzeige und Dateien:
fester Takt, Laufen, Stein schlagen in Reichweite, Speichern mit F5,
Laden mit F9, Pause mit Esc, automatisches Speichern alle zwei Minuten
und beim Schließen. Die Karte erscheint in Platzhalterfarben, bis in
M1d ein Stil gewählt ist. Ein Werkzeug macht Bildschirmfotos ohne
Handarbeit.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 11:53:51 +02:00
Steven Wroblewski 3761966303 SaveStore prüft jeden Schreibvorgang
Ein Speicherstand ersetzt den vorigen nur noch, wenn er vollständig
geschrieben und zurückgelesen wurde. Ein nicht gespeicherter
Spielerschlüssel wird gemeldet, und set_aside_save meldet, wenn nichts
verschoben wurde.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 11:42:31 +02:00
Steven Wroblewski ccf9813c80 Texte, Tasten und Dateien
Alle Texte stehen in einer Tabelle mit Deutsch und Englisch, die das
Spiel zur Laufzeit liest. Die Tasten werden in Code angelegt, nach
physischer Position. SaveStore verwaltet den Spielerschlüssel dieser
Installation, schreibt den Speicherstand über eine temporäre Datei,
legt unbrauchbare Stände beiseite und behält die zehn neuesten
Aufzeichnungen.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 11:26:12 +02:00
Steven Wroblewski 2607df8a97 Takt und Bewegung der Figur
TickClock rechnet Bildzeit in Takte um und holt nach einem Ruckler
höchstens fünf Takte auf. GridMotion bewegt einen Körper über das
Kachelraster und lässt ihn an Wänden entlanggleiten. Die Figur läuft
fünf Felder pro Sekunde, außerhalb des Gleichschritts.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 11:11:52 +02:00
Steven Wroblewski bd0f588714 Brücke zu Godot und Godot-Projekt
Die GDExtension grovewright-bridge gibt den Kern als Klasse GwSession
an GDScript weiter: Befehle einreihen, einen Takt ausführen, Karte und
Inventar lesen, speichern, laden, aufzeichnen. Eine Panik im Kern macht
die Sitzung defekt, statt einen guten Speicherstand zu überschreiben.
Das Godot-Projekt bekommt einen Testläufer, der auch Laufzeitfehler als
Fehlschlag zählt.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 10:46:23 +02:00
Steven Wroblewski 5dacbf2222 Aufzeichnung an den Weltschritt koppeln
Recording::step führt einen Takt aus und zeichnet ihn auf. Andere
Befehle als die ausgeführten können so nicht in eine Aufzeichnung
geraten. Aufzeichnung und Welt prüfen gegenseitig ihren Takt, und ein
beschädigter Startzustand fällt schon beim Einlesen auf.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 10:34:30 +02:00
Steven Wroblewski ef728cd100 Speicherstand mit Prüfsumme und Prüfung beim Laden
Snapshots tragen jetzt eine Prüfsumme (Format 2). Beim Wiederherstellen
werden Zustände abgelehnt, die die Regeln nie erzeugen: zu große Takte,
mehr als vier Spieler online, doppelte Schlüssel, unmögliche
Wartezeiten. World::load ersetzt den öffentlichen Aufruf von
set_all_offline. Die festgeschriebenen Werte sind bewusst angepasst und
um einen Offline-Spieler und das Kartenformat ergänzt.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 10:17:03 +02:00
Steven Wroblewski 8573a68901 Wächter für den Kern schärfen
Clippy sperrt im Kern Hash-Tabellen, Uhr, Dateien, Threads und
Umgebung über aufgelöste Pfade, auch bei Aliasen und gruppierten
Importen. Der Textwächter kennt globalen veränderlichen Zustand und
Prozesse, prüft sich selbst, und ein neuer Test hält die Abhängigkeiten
des Kerns leer.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-01 10:01:11 +02:00
Steven Wroblewski 5be2764276 Umsetzungsplan M1b: Brücke und Hülle
Zweiter von vier Plänen für Meilenstein 1. Acht Tasks härten den Kern
(Wächter, Speicherstand mit Prüfsumme und Prüfung beim Laden,
Aufzeichnung am Weltschritt) und bauen die GDExtension-Brücke sowie die
Godot-Hülle mit Takt, Figur, Karte, Anzeige, Texten, Dateien und
Startskript. Der Code im Plan ist vorab geprüft, jede Task-Grenze
einzeln.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:55:14 +02:00