# alox_test_game

Ein einfaches 2D-Pong-Spiel in **Godot 4.6**, gesteuert über Beschleunigungsdaten eines externen Geräts per **WebSocket**. Die Plattform unten wird horizontal bewegt; ein Ball prallt an Wänden, am Boden und an der Plattform ab.

## Voraussetzungen

- [Godot Engine 4.6](https://godotengine.org/) (getestet mit 4.6.x)
- Ein WebSocket-Server auf `localhost:9090`, Endpoint `/ws`
- Nachrichtenformat (JSON, ca. alle 16 ms):

```json
{"type":"accel","t":1779991116528186251,"success":true,"x":6772,"y":-2675,"z":14704}
```

Nur Nachrichten mit `"type": "accel"` und `"success": true` werden ausgewertet. Die Werte `x`, `y`, `z` sind ganzzahlige Rohwerte des Sensors (keine SI-Einheiten im Spiel).

## Start

```bash
godot --path /pfad/zu/alox-test-game
```

Beim Start läuft zuerst die **Kalibrierung**, danach das Spiel. Über **„Kalibrierung neu starten“** (oben rechts im Spiel) oder **„Kalibrierung starten“** (im Kalibrierungs-Overlay) kann die Kalibrierung jederzeit wiederholt werden.

## Projektstruktur

| Pfad | Rolle |
|------|--------|
| `scenes/main.tscn` | Hauptszene (UI, Plattform, Ball, Kalibrierungs-Overlay) |
| `scripts/main.gd` | WebSocket, Spielzustand, Plattformsteuerung, Ball-Update |
| `scripts/ball.gd` | Ballphysik (Wände, Boden, Plattformkollision) |
| `scripts/calibration_overlay.gd` | Kalibrierungs-UI und Phasenablauf |
| `scripts/accel_calibration.gd` | Auswertung der Messdaten → Achse, Vorzeichen, Schwellwert |
| `project.godot` | Projekteinstellungen, Viewport 1152×648 |

## Architektur (Überblick)

```mermaid
flowchart LR
  WS[WebSocket Server :9090] -->|JSON accel| Main[main.gd]
  Main -->|Roh-Vector3i| CalOverlay[calibration_overlay.gd]
  CalOverlay -->|Samples L/R| AccelCal[AccelCalibration]
  AccelCal -->|axis sign center threshold| Main
  Main -->|effektive Steuerung| Platform[Plattform]
  Main --> Ball[ball.gd]
```

**Zustände in `main.gd`:**

- `CALIBRATION` – Overlay sichtbar, Plattform/Ball/Slider ausgeblendet, Messwerte werden nur für die Kalibrierung gesammelt.
- `PLAYING` – normales Pong mit kalibrierter Achse und einstellbaren Slidern.

---

## Kalibrierung (technisch)

### Ziel

Das Gerät kann beliebig gehalten werden; Gravitation und Sensorausrichtung liefern oft große, konstante Anteile auf **x**, **y** und **z**. Die Kalibrierung ermittelt automatisch:

1. **Welche Achse** (`x`, `y` oder `z`) am stärksten auf *horizontale* Neigung reagiert, wenn der Nutzer dem Bildschirmanimation folgt.
2. **Welches Vorzeichen** „nach rechts neigen“ auf dem Bildschirm bedeutet.
3. **Ruhewert (`center`)** der gewählten Achse (Median aller Kalibrierungs-Samples).
4. **Vorgeschlagenen Schwellwert** für die Deadzone im Spiel (aus Streuung der Samples).

Ohne Kalibrierung wäre feste Nutzung von `accel.x` oft falsch oder würde in Ruhe dauerhaft eine Seitenrichtung erzeugen.

### Ablauf für den Nutzer

1. **WAITING** – Verbindung zu `ws://localhost:9090/ws` (optional manuell starten).
2. **RIGHT** (4,5 s) – Ein Kreis mit Nadel (Strich nach oben) bewegt sich mit Smoothstep von der Bildschirmmitte **140 px nach rechts**. Der Nutzer neigt das Gerät passend nach rechts.
3. **LEFT** (4,5 s) – Gleiche Animation **140 px nach links** vom Zentrum; Nutzer neigt nach links.
4. **DONE** – `AccelCalibration.analyze()`; bei Erfolg Signal `calibration_finished` → Spielstart.

Visuelle Animation (`calibration_overlay.gd`):

- Position des Kreises: `viewport_center + guide_offset`
- `guide_offset.x = ±TRAVEL_PX * smoothstep(t)` mit `t ∈ [0, 1]` über `PHASE_DURATION`
- Smoothstep: `t² * (3 - 2t)` (kein lineares Ruckeln)

Während RIGHT/LEFT ruft `main.gd` bei jedem empfangenen Accel-Paket `feed_accel(_accel)` auf; Samples landen in getrennten Arrays.

### Datensammlung

```gdscript
# accel_calibration.gd
record_right(accel)  → _samples_right
record_left(accel)   → _samples_left
```

Mindestens **20 Samples pro Phase** (`is_ready()`), typisch deutlich mehr bei ~60 Hz und 4,5 s Laufzeit.

### Auswertung (`analyze()`)

**Schritt 1 – Mittelwerte pro Phase**

\[
\bar{a}_{\text{right}} = \frac{1}{N_r}\sum_{i} a_i,\quad
\bar{a}_{\text{left}}  = \frac{1}{N_l}\sum_{i} a_i
\]

mit \(a_i = (x_i, y_i, z_i)\) als `Vector3`.

**Schritt 2 – Differenzvektor**

\[
\Delta = \bar{a}_{\text{right}} - \bar{a}_{\text{left}}
\]

Die Achse mit dem **größten Betrag** in \(\Delta\) wird gewählt:

\[
\text{axis} = \arg\max_{c \in \{x,y,z\}} |\Delta_c|
\]

**Schritt 3 – Vorzeichen**

\[
\text{sign} = \begin{cases}
+1 & \text{if } \Delta_{\text{axis}} \geq 0 \\
-1 & \text{if } \Delta_{\text{axis}} < 0
\end{cases}
\]

Damit „rechts neigen“ im Kalibrierungsrecht zu **positiver** Projektion auf die Steuerachse wird.

**Schritt 4 – Projektion (kalibrierter Skalar)**

Für jeden Rohvektor \(a\):

\[
p(a) = \text{sign} \cdot a_{\text{axis}}
\]

Implementiert als `project(accel)`.

**Schritt 5 – Ruhewert `center`**

Alle Samples aus RIGHT- und LEFT-Phase werden projiziert, sortiert; **`center` = Median** dieser Werte. Der Median ist robuster gegen Ausreißer als der Mittelwert, wenn der Nutzer in einer Phase ungleichmäßig neigt.

**Schritt 6 – Schwellwert-Vorschlag**

Für jede Projektion \(p_i\):

\[
d_i = |p_i - \text{center}|
\]

Median von \(d_i\) → `spread`; vorgeschlagener Schwellwert:

\[
\text{threshold} = \mathrm{clamp}(\text{spread} \cdot 1.35,\ 200,\ 12000)
\]

Der Faktor 1.35 lässt etwas Spielraum über die typische Ruhestreuung; Grenzen verhindern extremes Verhalten.

### Nutzung im Spiel

Nach der Kalibrierung in `_effective_accel()` (`main.gd`):

```gdscript
raw      = calibration.project(_accel)      # p(a)
centered = raw - calibration.center
if abs(centered) <= threshold_slider:
    return 0.0   # Deadzone
return sign(centered) * (abs(centered) - threshold)
```

Die Plattformgeschwindigkeit folgt dem effektiven Wert mit Skalierung über den Slider **Bewegungsstärke** (1–100 % → Faktor 0.01–1.0 auf die Zielgeschwindigkeit), Reibung in Ruhe, Begrenzung an den Bildschirmrändern.

### Grenzen und Annahmen

| Thema | Verhalten |
|--------|-----------|
| Nur eine Steuerachse | Es wird genau **eine** von x/y/z genutzt; kombinierte Neigung auf mehreren Achsen wird nicht modelliert. |
| Korrelation statt Regression | Achsenwahl über \(|\Delta_x|, |\Delta_y|, |\Delta_z|\), keine Kovarianzmatrix / PCA. |
| Symmetrische Phasen | Rechts- und Links-Phase gleich lang; Nutzer soll in beiden Phasen ähnlich stark neigen. |
| Keine Persistenz | Kalibrierung gilt nur bis Neustart der Anwendung (nicht in Datei gespeichert). |
| WebSocket optional in WAITING | „Kalibrierung starten“ ohne Live-Daten liefert keine sinnvollen Samples → `analyze()` schlägt fehl (< 20 Samples). |

### Kalibrierung neu starten

`main.gd` → `_restart_calibration()`:

- Zustand zurück auf `CALIBRATION`
- Spiel-UI ausblenden, `calibration_overlay.start()`
- Bei offenem WebSocket automatisch `notify_connected()` → Phasen starten

---

## Spielmechanik (Kurz)

- **Plattform:** horizontale Geschwindigkeit aus kalibriertem Accel (siehe oben).
- **Ball** (`ball.gd`): konstante Geschwindigkeit, Reflexion an linken/rechten/oben Wänden und am **unteren Spielfeldrand** (Viewport-Unterkante minus Rand). Kollision mit der **Oberseite** der Plattform; seitlicher Schwung durch `platform_vx * 0.45`.
- **Slider:** Schwellwert (Deadzone auf kalibrierter Achse), Bewegungsstärke (Empfindlichkeit).

## Lizenz / Hinweise

Privates Testprojekt; Server-Implementierung für `localhost:9090` ist nicht Teil dieses Repositories.
