fix: Restore all files lost during destructive rebase

A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,248 @@
// ==============================================
// ObstacleSpawner.cs - Hindernis-Generator
// ==============================================
// Spawnt Hindernisse, Items und Quiz-Trigger
// basierend auf Schwierigkeitsgrad.
using UnityEngine;
namespace BreakpilotDrive
{
public class ObstacleSpawner : MonoBehaviour
{
public static ObstacleSpawner Instance { get; private set; }
[Header("Hindernis-Prefabs")]
[SerializeField] private GameObject[] obstaclePrefabs; // Verschiedene Hindernisse
[SerializeField] private GameObject[] largObstaclePrefabs; // Grosse Hindernisse (2 Spuren)
[Header("Item-Prefabs")]
[SerializeField] private GameObject coinPrefab;
[SerializeField] private GameObject starPrefab;
[SerializeField] private GameObject shieldPrefab;
[Header("Quiz-Trigger-Prefabs")]
[SerializeField] private GameObject bridgePrefab;
[SerializeField] private GameObject treePrefab;
[SerializeField] private GameObject housePrefab;
[Header("Spawn-Einstellungen")]
[SerializeField] private float laneDistance = 3f;
[SerializeField] private float minObstacleSpacing = 10f;
[SerializeField] private float maxObstacleSpacing = 30f;
[Header("Wahrscheinlichkeiten (0-1)")]
[SerializeField] private float obstacleChance = 0.6f;
[SerializeField] private float coinChance = 0.3f;
[SerializeField] private float starChance = 0.05f;
[SerializeField] private float shieldChance = 0.02f;
[SerializeField] private float quizTriggerChance = 0.1f;
// Interner Zustand
private float lastObstacleZ = 0f;
private int quizTriggerCount = 0;
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
void Start()
{
// Wahrscheinlichkeiten von Difficulty laden
UpdateFromDifficulty();
}
private void UpdateFromDifficulty()
{
if (GameManager.Instance?.DifficultySettings != null)
{
var settings = GameManager.Instance.DifficultySettings;
obstacleChance = settings.obstacle_frequency;
// Power-Up Chance beeinflusst Items
float powerUpChance = settings.power_up_chance;
coinChance = powerUpChance * 0.8f;
starChance = powerUpChance * 0.15f;
shieldChance = powerUpChance * 0.05f;
}
}
// ==============================================
// Spawning auf Segment
// ==============================================
public void SpawnOnSegment(GameObject segment, float segmentStartZ)
{
float segmentLength = 50f; // Sollte mit TrackGenerator uebereinstimmen
float currentZ = segmentStartZ + minObstacleSpacing;
while (currentZ < segmentStartZ + segmentLength - minObstacleSpacing)
{
// Zufaelliger Abstand zum naechsten Objekt
float spacing = Random.Range(minObstacleSpacing, maxObstacleSpacing);
// Was spawnen?
SpawnAtPosition(segment.transform, currentZ);
currentZ += spacing;
}
}
private void SpawnAtPosition(Transform parent, float zPosition)
{
float roll = Random.value;
// Quiz-Trigger (selten, aber wichtig)
if (roll < quizTriggerChance && quizTriggerCount < 3)
{
SpawnQuizTrigger(parent, zPosition);
quizTriggerCount++;
return;
}
// Hindernis
roll = Random.value;
if (roll < obstacleChance)
{
SpawnObstacle(parent, zPosition);
}
// Items (koennen neben Hindernissen sein)
roll = Random.value;
if (roll < coinChance)
{
SpawnCoinRow(parent, zPosition);
}
else if (roll < coinChance + starChance)
{
SpawnItem(starPrefab, parent, zPosition, GetRandomLane());
}
else if (roll < coinChance + starChance + shieldChance)
{
SpawnItem(shieldPrefab, parent, zPosition, GetRandomLane());
}
}
// ==============================================
// Hindernisse
// ==============================================
private void SpawnObstacle(Transform parent, float zPosition)
{
if (obstaclePrefabs == null || obstaclePrefabs.Length == 0) return;
// Zufaelliges Hindernis und Spur waehlen
int prefabIndex = Random.Range(0, obstaclePrefabs.Length);
int lane = GetRandomLane();
// Manchmal grosses Hindernis (blockiert 2 Spuren)
if (largObstaclePrefabs != null && largObstaclePrefabs.Length > 0 && Random.value < 0.2f)
{
prefabIndex = Random.Range(0, largObstaclePrefabs.Length);
SpawnObstacleAtLane(largObstaclePrefabs[prefabIndex], parent, zPosition, lane);
}
else
{
SpawnObstacleAtLane(obstaclePrefabs[prefabIndex], parent, zPosition, lane);
}
}
private void SpawnObstacleAtLane(GameObject prefab, Transform parent, float zPosition, int lane)
{
float xPos = (lane - 1) * laneDistance;
Vector3 position = new Vector3(xPos, 0, zPosition);
GameObject obstacle = Instantiate(prefab, parent);
obstacle.transform.localPosition = position;
obstacle.tag = "Obstacle";
}
// ==============================================
// Items
// ==============================================
private void SpawnItem(GameObject prefab, Transform parent, float zPosition, int lane)
{
if (prefab == null) return;
float xPos = (lane - 1) * laneDistance;
Vector3 position = new Vector3(xPos, 0.5f, zPosition); // Leicht ueber dem Boden
GameObject item = Instantiate(prefab, parent);
item.transform.localPosition = position;
}
private void SpawnCoinRow(Transform parent, float zPosition)
{
if (coinPrefab == null) return;
int lane = GetRandomLane();
int coinCount = Random.Range(3, 6);
for (int i = 0; i < coinCount; i++)
{
SpawnItem(coinPrefab, parent, zPosition + (i * 2f), lane);
}
}
// ==============================================
// Quiz-Trigger
// ==============================================
private void SpawnQuizTrigger(Transform parent, float zPosition)
{
GameObject prefab = GetRandomTriggerPrefab();
if (prefab == null) return;
// Trigger sind meist in der Mitte oder ueber der Strasse
Vector3 position = new Vector3(0, 0, zPosition);
GameObject trigger = Instantiate(prefab, parent);
trigger.transform.localPosition = position;
trigger.tag = "QuizTrigger";
// VisualTrigger-Komponente hinzufuegen falls nicht vorhanden
if (trigger.GetComponent<VisualTrigger>() == null)
{
VisualTrigger vt = trigger.AddComponent<VisualTrigger>();
vt.TriggerType = GetTriggerTypeFromPrefab(prefab);
}
}
private GameObject GetRandomTriggerPrefab()
{
GameObject[] triggers = new GameObject[] { bridgePrefab, treePrefab, housePrefab };
var validTriggers = System.Array.FindAll(triggers, t => t != null);
if (validTriggers.Length == 0) return null;
return validTriggers[Random.Range(0, validTriggers.Length)];
}
private string GetTriggerTypeFromPrefab(GameObject prefab)
{
if (prefab == bridgePrefab) return "bridge";
if (prefab == treePrefab) return "tree";
if (prefab == housePrefab) return "house";
return "unknown";
}
// ==============================================
// Helper
// ==============================================
private int GetRandomLane()
{
return Random.Range(0, 3); // 0, 1, oder 2
}
public void ResetSpawner()
{
lastObstacleZ = 0f;
quizTriggerCount = 0;
UpdateFromDifficulty();
}
}
}

View File

@@ -0,0 +1,233 @@
// ==============================================
// TrackGenerator.cs - Endlose Streckengenerierung
// ==============================================
// Erzeugt endlose Streckenabschnitte durch
// Object-Pooling und Recycling.
using System.Collections.Generic;
using UnityEngine;
namespace BreakpilotDrive
{
public class TrackGenerator : MonoBehaviour
{
public static TrackGenerator Instance { get; private set; }
[Header("Track Prefabs")]
[SerializeField] private GameObject[] trackSegmentPrefabs; // Verschiedene Streckenabschnitte
[SerializeField] private GameObject startSegmentPrefab; // Start-Segment
[Header("Generierung")]
[SerializeField] private float segmentLength = 50f; // Laenge eines Segments
[SerializeField] private int visibleSegments = 5; // Anzahl sichtbarer Segmente
[SerializeField] private float despawnDistance = -20f; // Wann Segment recycelt wird
[Header("Bewegung")]
[SerializeField] private float baseSpeed = 10f; // Basis-Geschwindigkeit
private float currentSpeed;
// Object Pool
private List<GameObject> activeSegments = new List<GameObject>();
private Queue<GameObject> segmentPool = new Queue<GameObject>();
// Position
private float nextSpawnZ = 0f;
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
void Start()
{
currentSpeed = baseSpeed;
// Geschwindigkeit von Difficulty laden
if (GameManager.Instance?.DifficultySettings != null)
{
currentSpeed = GameManager.Instance.DifficultySettings.lane_speed;
}
// Initiale Segmente erstellen
InitializeTrack();
}
void Update()
{
if (GameManager.Instance?.CurrentState != GameState.Playing)
return;
// Segmente bewegen
MoveSegments();
// Alte Segmente recyceln
RecycleSegments();
// Neue Segmente spawnen
SpawnSegmentsIfNeeded();
}
// ==============================================
// Initialisierung
// ==============================================
private void InitializeTrack()
{
nextSpawnZ = 0f;
// Start-Segment
if (startSegmentPrefab != null)
{
SpawnSegment(startSegmentPrefab);
}
// Weitere Segmente fuer sichtbaren Bereich
for (int i = 0; i < visibleSegments; i++)
{
SpawnRandomSegment();
}
}
// ==============================================
// Segment-Spawning
// ==============================================
private void SpawnRandomSegment()
{
if (trackSegmentPrefabs == null || trackSegmentPrefabs.Length == 0)
{
Debug.LogWarning("Keine Track-Segment-Prefabs zugewiesen!");
return;
}
int index = Random.Range(0, trackSegmentPrefabs.Length);
SpawnSegment(trackSegmentPrefabs[index]);
}
private void SpawnSegment(GameObject prefab)
{
GameObject segment;
// Aus Pool holen oder neu erstellen
if (segmentPool.Count > 0)
{
segment = segmentPool.Dequeue();
segment.SetActive(true);
}
else
{
segment = Instantiate(prefab, transform);
}
// Position setzen
segment.transform.position = new Vector3(0, 0, nextSpawnZ);
nextSpawnZ += segmentLength;
activeSegments.Add(segment);
// Hindernisse und Items auf Segment spawnen
ObstacleSpawner.Instance?.SpawnOnSegment(segment, nextSpawnZ - segmentLength);
}
private void SpawnSegmentsIfNeeded()
{
// Pruefe ob wir mehr Segmente brauchen
float playerZ = 0; // Spieler ist bei Z=0, Welt bewegt sich
float maxVisibleZ = playerZ + (visibleSegments * segmentLength);
while (nextSpawnZ < maxVisibleZ)
{
SpawnRandomSegment();
}
}
// ==============================================
// Bewegung
// ==============================================
private void MoveSegments()
{
Vector3 movement = Vector3.back * currentSpeed * Time.deltaTime;
foreach (var segment in activeSegments)
{
segment.transform.position += movement;
}
// Auch nextSpawnZ anpassen
nextSpawnZ -= currentSpeed * Time.deltaTime;
}
// ==============================================
// Recycling
// ==============================================
private void RecycleSegments()
{
for (int i = activeSegments.Count - 1; i >= 0; i--)
{
if (activeSegments[i].transform.position.z < despawnDistance)
{
RecycleSegment(activeSegments[i]);
activeSegments.RemoveAt(i);
}
}
}
private void RecycleSegment(GameObject segment)
{
// Kinder-Objekte (Hindernisse, Items) entfernen
foreach (Transform child in segment.transform)
{
if (child.CompareTag("Obstacle") || child.CompareTag("Coin") ||
child.CompareTag("Star") || child.CompareTag("QuizTrigger"))
{
Destroy(child.gameObject);
}
}
// Segment deaktivieren und zurueck in Pool
segment.SetActive(false);
segmentPool.Enqueue(segment);
}
// ==============================================
// Geschwindigkeit
// ==============================================
public void SetSpeed(float speed)
{
currentSpeed = speed;
}
public float GetSpeed()
{
return currentSpeed;
}
public void IncreaseSpeed(float amount)
{
currentSpeed += amount;
}
// ==============================================
// Reset
// ==============================================
public void ResetTrack()
{
// Alle aktiven Segmente recyceln
foreach (var segment in activeSegments)
{
RecycleSegment(segment);
}
activeSegments.Clear();
// Neu initialisieren
nextSpawnZ = 0f;
currentSpeed = baseSpeed;
InitializeTrack();
}
}
}

View File

@@ -0,0 +1,57 @@
// ==============================================
// VisualTrigger.cs - Quiz-Trigger Objekt
// ==============================================
// Loest Quick-Quiz aus wenn Spieler durchfaehrt.
using UnityEngine;
namespace BreakpilotDrive
{
public class VisualTrigger : MonoBehaviour
{
[Header("Trigger-Konfiguration")]
[SerializeField] private string triggerType = "bridge"; // "bridge", "tree", "house"
public string TriggerType
{
get => triggerType;
set => triggerType = value;
}
private bool hasTriggered = false;
void OnTriggerEnter(Collider other)
{
if (hasTriggered) return;
if (other.CompareTag("Player"))
{
hasTriggered = true;
// Quick-Quiz ausloesen
if (QuizManager.Instance != null)
{
QuizManager.Instance.ShowQuickQuestion(triggerType);
}
Debug.Log($"Quiz-Trigger ausgeloest: {triggerType}");
}
}
void OnTriggerExit(Collider other)
{
// Reset fuer naechsten Durchlauf (falls Objekt recycelt wird)
if (other.CompareTag("Player"))
{
// Optional: Trigger nach kurzer Zeit wieder aktivieren
// hasTriggered = false;
}
}
// Wird aufgerufen wenn Objekt recycelt wird
void OnDisable()
{
hasTriggered = false;
}
}
}