extends Node func _ready() -> void: call_deferred("_run") func _run() -> void: var game = load("res://main.tscn").instantiate() get_tree().root.add_child(game) await get_tree().process_frame game.ui_language = "en" assert(game._ui("FREUNDESLISTE") == "FRIENDS") assert(game._ui("WELLE") == "WAVE") game.ui_language = "de" assert(game._ui("FREUNDESLISTE") == "FREUNDESLISTE") game._on_match_invite_received({"room_code": "ABC123", "from": "Testpilot"}) assert(game.pending_match_invite.room_code == "ABC123") game.pending_match_invite.clear() game.friend_return_mode = "matchmaking" game.multiplayer_manager.matchmaking_mode = "private_host" game.multiplayer_manager.matchmaking_state = "hosting" game.multiplayer_manager.private_room_code = "ABC123" assert(game._can_invite_friends()) game.multiplayer_manager.matchmaking_state = "lobby" assert(game._can_invite_friends(), "Im privaten Warteraum muss EINLADEN sichtbar bleiben.") game.multiplayer_manager.matchmaking_state = "offline" assert(not game._can_invite_friends(), "Ein alter Raumcode darf keine Einladung mehr ermöglichen.") game.coop_online_mode = true var base_snapshot := { "difficulty": 1, "reactor_lives": 30, "wave": 1, "wave_active": true, "enemies_to_spawn": 0, "wave_types_remaining": [], "towers": [], "enemies": [{"id": 4, "type": 0, "x": 100.0, "y": 200.0, "hp": 40.0, "max_hp": 40.0, "shield": false, "boss": false}], "shots": [] } game._apply_online_snapshot(base_snapshot) assert(game.enemies.size() == 1) assert(game.enemies[0].pos == Vector2(100, 200)) var first_enemy: Dictionary = game.enemies[0] var next_snapshot: Dictionary = base_snapshot.duplicate(true) next_snapshot["enemies"][0]["x"] = 200.0 next_snapshot["enemies"][0]["hp"] = 25.0 game._apply_online_snapshot(next_snapshot) assert(is_same(first_enemy, game.enemies[0]), "Gegnerdaten sollen zwischen Paketen wiederverwendet werden.") game._update_online_interpolation(0.05) assert(game.enemies[0].pos.x > 100.0 and game.enemies[0].pos.x < 200.0, "Die Position soll zwischen zwei Serverständen gleichmäßig übergehen.") game._update_online_interpolation(0.1) assert(is_equal_approx(game.enemies[0].pos.x, 200.0)) game.online_projectiles.append({"pos": Vector2.ZERO, "target": Vector2(30, 0), "speed": 920.0, "color": Color.WHITE}) game._update_online_projectiles(0.1) assert(game.online_projectiles.is_empty(), "Geschosse dürfen bei einem langen Frame nicht am Ziel vorbeipendeln.") var online_manager: OnlineManager = game.get_node("OnlineManager") var received: Array = [] online_manager.match_snapshot_received.connect(func(snapshot: Dictionary) -> void: received.append(snapshot)) online_manager.pending_snapshot_payloads.append(JSON.stringify({"tick": 1, "shots": [{"id": 1}]})) online_manager.pending_snapshot_payloads.append(JSON.stringify({"tick": 2, "shots": [{"id": 2}]})) online_manager._flush_pending_snapshots() assert(received.size() == 1 and received[0].tick == 2, "Nur der neueste Spielstand darf angewendet werden.") assert(received[0].shots.size() == 2, "Schusseffekte aus zusammengefassten Spielständen müssen erhalten bleiben.") received.clear() online_manager.current_match = {"match_id": "test-match"} for tick in 8: online_manager._on_match_state({"match_id": "test-match", "op_code": online_manager.OP_SNAPSHOT, "data": JSON.stringify({"tick": tick, "shots": [{"id": tick}]})}) assert(online_manager.pending_snapshot_payloads.size() == online_manager.MAX_PENDING_SNAPSHOTS, "Die Warteschlange muss auch nach einem längeren Rückstau begrenzt bleiben.") online_manager._flush_pending_snapshots() assert(received.size() == 1 and received[0].tick == 7) assert(received[0].shots.size() == online_manager.MAX_PENDING_SNAPSHOTS) var diagnostic_bundle: String = online_manager.get_diagnostic_bundle() assert(diagnostic_bundle.contains("\"event\":\"window\""), "Die Online-Diagnose muss einen kopierbaren Messbericht erzeugen.") assert(diagnostic_bundle.contains("\"queue_dropped\":3"), "Verworfene Zustände im Rückstau müssen erfasst werden.") online_manager._finish_match_diagnostics("test_finished") assert(online_manager.diagnostic_match_key.is_empty()) assert(online_manager.diagnostic_finished_match_key == "test-match".sha256_text().substr(0, 12)) online_manager._on_match_state({"match_id": "test-match", "op_code": online_manager.OP_SNAPSHOT, "data": JSON.stringify({"tick": 8, "shots": []})}) assert(online_manager.diagnostic_file == null, "Nach dem Rundenende darf kein zweiter Diagnosebericht geöffnet werden.") game._select_map(0) game._prepare_draw_geometry() assert(not game.draw_geometry_dirty and not game.road_tile_cache.is_empty()) assert(not game.build_cell_cache.is_empty()) game._select_map(1) assert(game.draw_geometry_dirty, "Ein Kartenwechsel muss die Weggeometrie erneuern.") game._prepare_draw_geometry() assert(not game.draw_geometry_dirty) game.coop_mode = true game._configure_coop_arena() game._prepare_draw_geometry() var partner_cells: Array = game.build_cell_cache.filter(func(cell: Dictionary) -> bool: return cell.partner) assert(not partner_cells.is_empty(), "Die gespiegelte Koop-Seite braucht eigene Baufelder.") assert(partner_cells.all(func(cell: Dictionary) -> bool: return cell.rect.get_center().x > game.COOP_MIRROR_X), "Für Spieler 1 muss nur die rechte Seite als Partnerseite markiert sein.") game.coop_online_mode = true game.local_player_slot = 1 game.draw_geometry_dirty = true game._prepare_draw_geometry() partner_cells = game.build_cell_cache.filter(func(cell: Dictionary) -> bool: return cell.partner) assert(not partner_cells.is_empty() and partner_cells.all(func(cell: Dictionary) -> bool: return cell.rect.get_center().x < game.COOP_MIRROR_X), "Für Spieler 2 muss nur die linke Seite als Partnerseite markiert sein.") game.camera_offset.x = game.COOP_WORLD_WIDTH - game.SCREEN.x game._toggle_coop_side() assert(game.status_text == "PARTNER-SEITE", "Der Seitenwechsel muss Spieler 2 links als Partnerseite benennen.") game._toggle_coop_side() assert(game.status_text == "DEINE SEITE", "Der Seitenwechsel muss Spieler 2 rechts als eigene Seite benennen.") print("VOID_BASTION_ONLINE_SNAPSHOT_OK") get_tree().quit()