This commit is contained in:
Team3
2026-09-04 00:06:51 +02:00
parent 8c7da42e64
commit 8b60164592
8 changed files with 690 additions and 73 deletions

View File

@@ -34,7 +34,12 @@ func _ready() -> void:
await test_tagesablauf()
await test_wege_machbar()
await test_geist()
await test_verfolgung()
await test_gehen_bei_tempo()
await test_diagnose()
await test_notieren_im_spiel()
await test_tagesablauf_anzeige()
await test_ablauf_im_hud()
await test_systeme()
await test_tempo_des_traums()
await test_cooldown()
@@ -691,6 +696,110 @@ func test_geist() -> void:
p(lena.global_position.distance_to(davor) < 2.0, "Menschen nehmen den Geist nicht wahr")
## Bei hohem Tempo wird der Schritt je Bild größer als die Ankunftsschwelle.
## Wer dann über den Wegpunkt hinausläuft, pendelt um ihn herum statt anzukommen.
func test_gehen_bei_tempo() -> void:
abschnitt("Gehen bleibt auch im Schnelllauf ruhig")
ui().schliessen()
var mensch: Bewohner = welt().humans[0]
var stufe_kehrt: Dictionary = {}
for stufe in [1, 3]:
Game.tempo_setzen(stufe)
Game.time_h = 12.9
for i in range(400):
schritte(1, 1.0 / 60.0)
if not mensch._path.is_empty():
break
var kehrt := 0
var bewegt := 0
var vorher: Vector3 = mensch.global_position
var richtung := Vector3.ZERO
for i in range(600):
schritte(1, 1.0 / 60.0)
if mensch._path.is_empty():
continue
var d: Vector3 = mensch.global_position - vorher
d.y = 0.0
if d.length() > 0.001:
bewegt += 1
if richtung != Vector3.ZERO and d.normalized().dot(richtung) < -0.5:
kehrt += 1
richtung = d.normalized()
vorher = mensch.global_position
stufe_kehrt[stufe] = kehrt
var weite := Bewohner.GEHTEMPO * (1.0 / 60.0) * Game.faktor_von(stufe)
p(kehrt <= 4, "Tempo %d (%d×, %.2f je Bild): %d Kehrtwenden in %d Bildern" %
[stufe, int(Game.faktor_von(stufe)), weite, kehrt, bewegt])
p(int(stufe_kehrt[3]) <= int(stufe_kehrt[1]) + 4,
"Schnelllauf zappelt nicht mehr als Normaltempo (%d gegen %d)" %
[stufe_kehrt[3], stufe_kehrt[1]])
Game.tempo_setzen(1)
## Sich an einen Menschen hängen: der Geist begleitet ihn durch seinen Tag,
## statt ihm hinterherzulaufen. Damit bleibt er die ganze Zeit beobachtbar.
func test_verfolgung() -> void:
abschnitt("Als Geist einen Menschen begleiten")
p(InputMap.has_action("folgen") and not InputMap.action_get_events("folgen").is_empty(),
"Eigene Taste fürs Folgen")
ui().schliessen()
main.player.set_mode(Player.Mode.GEIST)
main.player.look_enabled = true
main.player.yaw = 0.0
var mensch: Bewohner = welt().humans[0]
Game.active_mission = str(mensch.data["id"])
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
main.player.velocity = Vector3.ZERO
main._process(0.05)
p(main._fokus_mensch == mensch, "Der Mensch ist im Fokus")
main._verfolgen()
p(main.player.folgt == mensch, "F hängt den Geist an ihn")
# Er lebt seinen Tag weiter, der Geist bleibt dran
var weiteste := 0.0
var gelaufen := false
var im_fokus := 0
for i in range(900):
schritte(1)
await get_tree().physics_frame
if not mensch._path.is_empty():
gelaufen = true
if main._fokus_mensch == mensch:
im_fokus += 1
weiteste = maxf(weiteste,
main.player.global_position.distance_to(mensch.global_position))
p(gelaufen, "Er wechselt in dieser Zeit den Ort")
p(weiteste < 6.0, "Der Geist bleibt die ganze Zeit dicht dabei (höchstens %.1f)" % weiteste)
p(im_fokus > 850, "Und behält ihn im Fokus (%d von 900 Schritten)" % im_fokus)
p(main._fokus_mensch == mensch or main.player.folgt == mensch,
"Unterwegs bleibt er notierbar")
# Eigene Bewegung löst die Verfolgung
Input.action_press("vor")
for i in range(10):
await get_tree().physics_frame
Input.action_release("vor")
p(main.player.folgt == null, "Wer selbst losgeht, lässt ihn los")
# Die Taste löst ebenfalls
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 2.0)
main.player.velocity = Vector3.ZERO
main._process(0.05)
main._verfolgen()
p(main.player.folgt == mensch, "Wieder angehängt")
main._verfolgen()
p(main.player.folgt == null, "F lässt wieder los")
# In der Traumwelt gibt es niemanden zu verfolgen
main._traumwelt()
await get_tree().process_frame
main._verfolgen()
p(main.player.folgt == null, "In der Traumwelt hängt sich der Läufer an niemanden")
main._erde()
await get_tree().process_frame
## Woran der Traum scheitert, steht nirgends. Man findet es, indem man
## dabeisteht -- awareness training, bevor irgendetwas ersetzt wird.
func test_diagnose() -> void:
@@ -699,7 +808,7 @@ func test_diagnose() -> void:
Game.active_mission = id
Game.systeme.clear()
Game.erkannt_slots.clear()
Game.beobachtung.clear()
Game.notiz_sperre = 0.0
var urs: Array = Game.ursachen_von(id)
p(urs.size() >= 2 and urs.size() <= 3,
@@ -739,11 +848,19 @@ func test_diagnose() -> void:
p(not Game.ursache_bekannt(id, ursache), "Und die Ursache ist noch unbekannt")
# Solange man nicht lange genug dabeisteht, passiert nichts
Game.beobachten(id, blocker_nr, Game.ERKENNEN_SEK * 0.4)
p(not Game.erkannt(id, blocker_nr), "Kurz hinschauen reicht nicht")
Game.beobachten(id, blocker_nr, Game.ERKENNEN_SEK)
p(Game.erkannt(id, blocker_nr), "Nach %.0f Sekunden dabeistehen zeigt es sich" %
Game.ERKENNEN_SEK)
# Zusehen allein deckt nichts auf -- der Spieler muss den Moment benennen
for i in range(60):
schritte(1)
p(not Game.erkannt(id, blocker_nr), "Bloßes Dabeistehen deckt nichts auf")
p(Game.notieren(id, neutral_nr) == "daneben",
"Im freien Slot geht die Notiz daneben")
p(Game.notiz_sperre > 0.0, "Ein Fehlgriff sperrt kurz (%.0f s)" % Game.notiz_sperre)
p(Game.notieren(id, blocker_nr) == "gesperrt", "Solange nimmt er keine Notiz an")
Game.notiz_sperre = 0.0
p(Game.notieren(id, blocker_nr) == "treffer", "Im Blocker-Slot sitzt sie")
p(Game.erkannt(id, blocker_nr), "Und deckt auf, was dort läuft")
p(is_equal_approx(Game.notiz_sperre, 0.0), "Ein Treffer sperrt nicht")
p(Game.notieren(id, blocker_nr) == "bekannt", "Zweimal notieren bringt nichts Neues")
p(Game.ursache_bekannt(id, ursache),
"Und damit die Ursache: %s" % Ursachen.LISTE[ursache]["name"])
@@ -761,6 +878,226 @@ func test_diagnose() -> void:
## Gegen eine Ursache baut man ein System. Passt die Belohnung, hält es besser.
## Der Ablauf steht auch im HUD, ohne dass man ein Panel öffnen muss.
func test_ablauf_im_hud() -> void:
abschnitt("Der Ablauf steht im HUD")
var id := "mensch_0"
Game.gesehen.clear()
Game.erkannt_slots.clear()
Game.systeme.clear()
Game.active_mission = ""
ui().schliessen()
ui()._hud_aktualisieren()
p(not ui()._ablauf.visible, "Ohne Mission zeigt das HUD keinen Ablauf")
Game.active_mission = id
Game.time_h = 9.5
ui()._hud_aktualisieren()
p(ui()._ablauf.visible, "Mit Mission schon")
p(ui()._zellen.size() == 24, "Ein Feld je Stunde (%d)" % ui()._zellen.size())
p(ui()._ablauf_titel.text.contains("0 – 23"), "Beschriftet von 0 bis 23 Uhr")
p(ui()._ablauf_titel.text.contains("24 Stunden unbekannt"),
"Am Anfang ist alles unbekannt: %s" % ui()._ablauf_titel.text)
var unbekannt: Color = ui()._ablauf_farbe(id, Game.slot_bei_stunde(id, 9))
var nr := Game.slot_bei_stunde(id, 9)
Game.sehen(id, nr)
ui()._hud_aktualisieren()
p(ui()._ablauf_farbe(id, nr) != unbekannt, "Eine gesehene Stunde färbt sich um")
p(not ui()._ablauf_titel.text.contains("24 Stunden"),
"Und der Zähler geht runter: %s" % ui()._ablauf_titel.text)
p(ui()._ablauf_jetzt.text.begins_with("09 Uhr"),
"Darunter steht die laufende Stunde: %s" % ui()._ablauf_jetzt.text)
# Die laufende Stunde ist hervorgehoben
var jetzt: ColorRect = ui()._zellen[9]
var andere: ColorRect = ui()._zellen[8]
p(jetzt.custom_minimum_size.y > andere.custom_minimum_size.y,
"Das Feld der laufenden Stunde steht heraus")
# Ein benannter Blocker sieht anders aus als eine harmlose Stunde
var blocker_nr := -1
var frei_nr := -1
for n in range(Game.slots(id).size()):
var g := Game.slot_grund(id, n)
if Ursachen.ist_blocker(g) and blocker_nr < 0:
blocker_nr = n
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and frei_nr < 0:
frei_nr = n
Game.sehen(id, blocker_nr)
Game.sehen(id, frei_nr)
var vor_notiz: Color = ui()._ablauf_farbe(id, blocker_nr)
Game.notieren(id, blocker_nr)
p(ui()._ablauf_farbe(id, blocker_nr) != vor_notiz,
"Notiert wechselt der Blocker noch einmal die Farbe")
p(ui()._ablauf_farbe(id, blocker_nr) != ui()._ablauf_farbe(id, frei_nr),
"Und hebt sich von einer harmlosen Stunde ab")
Game.notiz_sperre = 0.0
## Der Ablauf 0-23 Uhr: am Anfang leer, füllt sich durchs Dabeisein.
func test_tagesablauf_anzeige() -> void:
abschnitt("Der Tag steht als Ablauf von 0 bis 23 Uhr")
var id := "mensch_0"
Game.active_mission = id
Game.gesehen.clear()
Game.erkannt_slots.clear()
Game.systeme.clear()
Game.notiz_sperre = 0.0
# Jede Stunde des Tages hat einen Slot, und jeder Tag ist derselbe
var luecken := 0
for stunde in range(24):
if Game.slot_bei_stunde(id, stunde) < 0:
luecken += 1
p(luecken == 0, "Alle 24 Stunden sind belegt")
Game.day = 1
var tag_eins: Array = []
for stunde in range(24):
tag_eins.append(Game.slot_bei_stunde(id, stunde))
Game.day = 9
var tag_neun: Array = []
for stunde in range(24):
tag_neun.append(Game.slot_bei_stunde(id, stunde))
p(tag_eins == tag_neun, "Tag 9 sieht aus wie Tag 1 — es gibt keine Wochentage")
Game.day = 1
ui().systeme_panel(id)
var zeilen := _knopf_texte()
p(zeilen.contains("00 – "), "Der Ablauf beginnt um 00 Uhr")
p(zeilen.contains(" – 24"), "Und reicht bis 24 Uhr")
var leer := 0
for stunde in range(24):
if not Game.gesehen_slot(id, Game.slot_bei_stunde(id, stunde)):
leer += 1
p(leer == 24, "Am Anfang ist jede der 24 Stunden unbekannt (%d)" % leer)
p(zeilen.contains("—") and not zeilen.contains("schläft"),
"Und im Ablauf steht nur ein Strich")
ui().schliessen()
# Dabeistehen füllt genau die Stunde, in der man steht
var mensch: Bewohner = null
for m in welt().humans:
if m.data["id"] == id:
mensch = m
var frei_nr := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if g != "schlaeft" and not Ursachen.ist_blocker(g):
frei_nr = nr
break
await _hin_zu(mensch, float(Game.slots(id)[frei_nr][0]) + 0.5)
main._process(0.05)
p(Game.gesehen_slot(id, frei_nr), "Wer dabeisteht, füllt die Stunde")
ui().systeme_panel(id)
p(_knopf_texte().contains(Orte.text(Game.slot_grund(id, frei_nr))),
"Sie steht jetzt im Ablauf: %s" % Orte.text(Game.slot_grund(id, frei_nr)))
ui().schliessen()
# Ein Blocker bleibt ein Fragezeichen, bis er benannt ist
var blocker_nr := -1
for nr in range(Game.slots(id).size()):
if Ursachen.ist_blocker(Game.slot_grund(id, nr)):
blocker_nr = nr
break
Game.sehen(id, blocker_nr)
ui().systeme_panel(id)
p(_knopf_texte().contains("?"), "Was schiefläuft, steht erst als Fragezeichen da")
ui().schliessen()
Game.notieren(id, blocker_nr)
ui().systeme_panel(id)
var b: Dictionary = Ursachen.BLOCKER[Game.slot_grund(id, blocker_nr)]
p(_knopf_texte().contains(str(b["name"])),
"Nach dem Notieren steht dort, was sie tut")
p(_knopf_texte().contains(str(Ursachen.LISTE[b["ursache"]]["name"])),
"Und warum: %s" % Ursachen.LISTE[b["ursache"]]["name"])
ui().schliessen()
## Der ganze Weg: dabeistehen, den Moment einschätzen, R drücken.
func test_notieren_im_spiel() -> void:
abschnitt("Notieren ist eine Entscheidung, kein Timer")
var id := "mensch_0"
Game.active_mission = id
Game.erkannt_slots.clear()
Game.notiz_sperre = 0.0
ui().schliessen()
main.player.set_mode(Player.Mode.GEIST)
main.player.look_enabled = true
var blocker_nr := -1
var frei_nr := -1
for nr in range(Game.slots(id).size()):
var g := Game.slot_grund(id, nr)
if Ursachen.ist_blocker(g) and blocker_nr < 0:
blocker_nr = nr
elif g != "schlaeft" and not Ursachen.ist_blocker(g) and frei_nr < 0:
frei_nr = nr
p(blocker_nr >= 0 and frei_nr >= 0, "Ihr Tag hat einen Blocker und einen freien Slot")
var mensch: Bewohner = null
for m in welt().humans:
if m.data["id"] == id:
mensch = m
# Im freien Slot danebengegriffen
await _hin_zu(mensch, float(Game.slots(id)[frei_nr][0]) + 0.5)
p(mensch.slot == frei_nr, "Sie steht im freien Slot (%d)" % mensch.slot)
main._notieren()
p(not Game.erkannt(id, blocker_nr), "Hier gibt es nichts zu notieren")
p(Game.notiz_sperre > 0.0, "Der Fehlgriff kostet eine Sperre")
# Sperre läuft in Echtzeit ab
for i in range(200):
main._process(0.05)
p(is_equal_approx(Game.notiz_sperre, 0.0), "Sie läuft von allein ab")
# Im Blocker-Slot getroffen
await _hin_zu(mensch, float(Game.slots(id)[blocker_nr][0]) + 0.5)
p(mensch.slot == blocker_nr, "Jetzt steht sie im Blocker-Slot (%d)" % mensch.slot)
p(main._fokus_mensch == mensch, "Und ist in Reichweite")
main._notieren()
p(Game.erkannt(id, blocker_nr), "R notiert, was hier schiefläuft: %s" %
Orte.text(Game.slot_grund(id, blocker_nr)))
# Ein fremder Mensch geht niemanden etwas an
Game.erkannt_slots.clear()
Game.notiz_sperre = 0.0
var fremd: Bewohner = null
for m in welt().humans:
if m.data["id"] != id:
fremd = m
main.player.folgt = null
main.player.global_position = fremd.global_position + Vector3(0, 1.5, 1.5)
main._process(0.05)
main._notieren()
p(Game.erkannt_slots.is_empty(),
"Ohne angenommene Mission notiert er bei %s nichts" % fremd.data["name"])
p(is_equal_approx(Game.notiz_sperre, 0.0), "Und das kostet auch keine Sperre")
# Aus der Verfolgung heraus geht es ebenfalls
Game.erkannt_slots.clear()
await _hin_zu(mensch, float(Game.slots(id)[blocker_nr][0]) + 0.5)
main._verfolgen()
p(main.player.folgt == mensch, "An sie gehängt")
main._notieren()
p(Game.erkannt(id, blocker_nr), "Auch im Vorbeischweben lässt sich notieren")
main.player.folgt = null
## Stellt den Geist zur genannten Stunde neben den Menschen.
func _hin_zu(mensch: Bewohner, stunde: float) -> void:
for i in range(600):
schritte(1, 0.05, stunde)
if mensch._path.is_empty():
break
main.player.folgt = null
main.player.global_position = mensch.global_position + Vector3(0, 1.5, 1.5)
main.player.velocity = Vector3.ZERO
await get_tree().process_frame
main._process(0.05)
func test_systeme() -> void:
abschnitt("Systeme — gegen die Ursache, nicht gegen die Tätigkeit")
var id := "mensch_0"
@@ -1612,7 +1949,7 @@ func _erster_traum_tage(saat: int) -> int:
Game.known.clear()
Game.systeme.clear()
Game.erkannt_slots.clear()
Game.beobachtung.clear()
Game.notiz_sperre = 0.0
Game.progress.clear()
Game.completed.clear()
Game.active_mission = ""