# Lindic Score — gemeinsamer Scoring-Server

Modul-URL: `https://services.lindic.net/modules/score/lindic-score.js`  
API-Basis: `https://services.lindic.net/api/score/`  
Config: `GET /api/score/config.php`

## Überblick

Der Scoreserver speichert Spielstände zentral in MariaDB (`services`) für alle PWA-Spiele.

- **Spieler (Hybrid):** anonym per Geräte-UUID + Bearer-Token; optional später Konto verknüpfen
- **Sparse Felder:** Spiele speichern nur benötigte Werte (`points`/`score`, `level`, `time_ms`, `amount`, `challenge`, `game_version`, …)
- **Offline:** Client queued lokal und sync’t bei Online-Rückkehr (Idempotency-Keys)
- **Leaderboard:** weltweite Top-N pro `app`

## Schnellstart (Browser)

```js
import { LindicScore } from 'https://services.lindic.net/modules/score/lindic-score.js';

const score = new LindicScore({ app: 'phoenix', gameVersion: '1.0.0' });
await score.ensurePlayer({ name: 'Pilot', country: 'CH' });
await score.submitScore({ score: 1200, level: 3 });
const board = await score.fetchLeaderboard({ top: 50 });
```

`app` muss in `config/apps.json` mit erlaubten `origins` stehen (z. B. `https://pwa.lindic.net`).

## Auth

1. `POST /api/score/player/register.php`  
   Body: `{ "app": "phoenix", "device_key": "<uuid>", "name"?: "...", "country"?: "CH", ... }`  
   Header: `Origin` muss zur App passen.  
   Antwort: `{ player, player_token }` — Token nur gehasht auf dem Server.

2. Schreibende Calls: `Authorization: Bearer <player_token>`

3. Konto (optional):
   - `POST /api/score/account/register.php` — `{ username, password, email? }`
   - `POST /api/score/account/login.php` — `{ username|login, password, device_key? }`
   - `POST /api/score/account/link.php` — Bearer + `{ username, password }` oder `{ create: true, ... }`

## Scores

### Submit

`POST /api/score/scores/submit.php`

```json
{
  "app": "phoenix",
  "idempotency_key": "local-1710000000",
  "score": 1200,
  "level": 3,
  "time_ms": 90000,
  "amount": null,
  "challenge": "solo",
  "game_version": "1.0.0",
  "scored_at": "2026-08-11T12:00:00Z",
  "name": "Pilot",
  "country": "CH"
}
```

Pflicht: `app`, `idempotency_key`, `score` oder `points` (innerhalb Game-Limits).

### Sync (Batch)

`POST /api/score/scores/sync.php`  
`{ "app": "phoenix", "scores": [ { ... }, ... ] }`

### Leaderboard

`GET /api/score/scores/leaderboard.php?app=phoenix&top=50&challenge=&version=`

Öffentliche Felder: Name, Land/Nationalität, Punkte, Level, Zeit, Challenge, Version — keine sensiblen Profildaten.

## Profilfelder (Spieler)

`name`/`display_name`, `age`, `birth_date`, `gender`, `language`, `nationality`, `country`, `created_at`

Selbst lesen/ändern: `GET|PATCH /api/score/player/me.php` (Bearer).

## Sicherheit

- CORS-Allowlist, kein `*`
- Rate-Limits (IP + Spieler + nginx)
- Game-Limits in Tabelle `games` (`max_points`, `max_level`, …)
- Idempotency gegen Doppel-Sync
- Prepared Statements, Token nur als SHA-256, Audit-IPs gehasht

## App registrieren

Eintrag in [`config/apps.json`](../../config/apps.json):

```json
"meinspiel": {
  "secret": "<server-only>",
  "label": "Mein Spiel",
  "origins": ["https://pwa.lindic.net"]
}
```

Zusätzlich Zeile in DB-Tabelle `games` (oder automatisch beim ersten Submit, wenn App bekannt).
