Files
ashen-realms/docs/playable-slices/Ashen Realms – Playable Slice 0.13_ Realtime Game Events Foundation.md
Bastian Wagner 221819880c arts und agents
2026-08-21 17:32:05 +02:00

1738 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ashen Realms Playable Slice 0.13
## Realtime Game Events Foundation
**Status:** Implementation Specification
**Slice:** 0.13
**Scope:** Zentraler WebSocket-Kanal für serverinitiierte Game Events
**Primary Use Case:** Combat Events
**Architecture:** REST Commands + WebSocket Events + server authoritative state
---
# 1. Ziel
Slice 0.13 führt eine zentrale Realtime-Kommunikationsschicht für Ashen Realms ein.
Bis einschließlich Slice 0.12 funktioniert das Spiel primär request-basiert:
```text
Client
→ REST Request
→ Server verarbeitet
→ Response
```
Das reicht für:
- Reisen
- Jagd
- Einzelspieler-Kämpfe
- Inventar
- Equipment
- Quests
- Händler
- Charakterverwaltung
Langfristig entstehen jedoch Situationen, bei denen sich der Spielzustand verändern kann, **ohne dass der aktuelle Client selbst einen Request ausgelöst hat**.
Beispiele:
- ein anderer Spieler führt im gemeinsamen Kampf eine Aktion aus
- ein NPC führt verzögert eine Kampfaktion aus
- ein anderer Spieler ist nun am Zug
- ein Gruppenmitglied tritt einem Kampf bei
- ein Begleiter oder Pet führt eine Aktion aus
- der Kampf endet durch die Aktion eines anderen Beteiligten
- später verändert ein externer Effekt Charakterwerte
- Gruppen-, Quest- oder Weltzustände ändern sich
Dafür wird ein zentraler WebSocket-Kanal eingeführt.
Grundsatz:
> **REST beschreibt, was der Spieler tun möchte. WebSocket Events beschreiben, was im Spiel passiert ist.**
---
# 2. Nicht-Ziel dieses Slices
Slice 0.13 führt **kein vollständiges Multiplayer-Kampfsystem** ein.
Noch nicht Teil dieses Slices:
- mehrere menschliche Spieler im selben Kampf
- mehrere Gegner in einem Kampf
- Pets
- NPC-Begleiter
- Party-System
- PvP
- Chat
- Location Presence
- Welt-Events
- globale Tick-Simulation
- WebSocket-basierte HP-Regeneration
- vollständige Umstellung bestehender REST-Endpunkte auf WebSockets
Der bestehende 1-vs-1-Combat bleibt funktional bestehen.
0.13 schafft ausschließlich die technische Grundlage, auf der spätere Mehrteilnehmer-Systeme aufbauen können.
---
# 3. Architekturprinzip
Ashen Realms verwendet ab Slice 0.13 drei unterschiedliche Mechanismen für unterschiedliche Arten von Zustand.
## 3.1 REST Commands und Reads
REST bleibt verantwortlich für explizite Spieleraktionen.
Beispiele:
```text
POST /api/travel
POST /api/hunts
POST /api/combats/:id/actions
POST /api/equipment
GET /api/characters/me
GET /api/inventory
GET /api/combats/:id
```
Der Client sendet weiterhin keine berechneten Spielwerte an den Server.
Beispiel:
```json
{
"action": "ATTACK"
}
```
Nicht:
```json
{
"action": "ATTACK",
"damage": 27
}
```
---
## 3.2 WebSocket diskrete serverseitige Ereignisse
WebSocket wird verwendet, wenn der Server einen Client über ein Ereignis informieren muss.
Beispiele:
```text
combat.action.resolved
combat.turn.changed
combat.finished
später:
character.vitals.changed
party.member.joined
quest.updated
location.player.joined
world.event.started
```
---
## 3.3 Zeitstempel kontinuierlich ableitbarer Zustand
Zeitabhängige Werte werden weiterhin nicht permanent übertragen.
Beispiele:
- HP-Regeneration
- Reisezeit
- Cooldowns
- Buff-Dauer
- Respawn-Zeit
Diese Systeme verwenden weiterhin:
```text
Basiszustand
+
Zeitstempel
+
Regel
```
Der Client darf daraus die Darstellung interpolieren.
Der Server bleibt für den tatsächlichen Zustand autoritativ.
---
# 4. Zentrale Regel
Es gibt **eine WebSocket-Verbindung pro eingeloggtem Client**.
Nicht:
```text
/combat websocket
/character websocket
/party websocket
```
Sondern:
```text
/events
```
Darüber werden verschiedene fachliche Event-Typen transportiert.
---
# 5. Zielarchitektur
```text
Angular Client
|
| REST Commands
v
NestJS Controllers
|
v
Domain Services
|
+--------------------------+
| |
v v
PostgreSQL GameEventPublisher
|
v
EventsGateway
|
| WebSocket
v
Angular Client
```
Beispiel Combat:
```text
Spieler klickt Angriff
|
v
POST /api/combats/:id/actions
|
v
CombatService
|
├── CombatEngine
├── State speichern
├── CombatEvents speichern
└── Transaction Commit
|
v
CombatEventPublisher
|
v
EventsGateway
|
v
combat.action.resolved
```
---
# 6. Wichtigste Architekturregel
Ein WebSocket-Event darf erst veröffentlicht werden, **nachdem der zugehörige persistente Zustand erfolgreich gespeichert wurde**.
Nicht:
```text
Event senden
→ danach DB speichern
```
Sondern:
```text
DB Transaction
→ Commit
→ Event veröffentlichen
```
Dadurch gilt:
> Wenn ein Client ein Event erhält, existiert der gemeldete Zustand bereits autoritativ auf dem Server.
---
# 7. WebSocket-Verbindung
Endpoint:
```text
/events
```
Die konkrete technische Umsetzung darf beispielsweise mit NestJS WebSocket Gateway und Socket.IO oder nativen WebSockets erfolgen.
Für V1 sollte die einfachere, robuste NestJS-Integration bevorzugt werden.
---
# 8. Authentifizierung
Die WebSocket-Verbindung muss authentifiziert sein.
Der Server muss aus der Verbindung mindestens bestimmen können:
```text
userId
characterId
```
Ein Client darf niemals selbst angeben:
```text
Ich bin Character X.
```
ohne dass der Server diese Zuordnung aus der Authentifizierung validiert.
---
# 9. Connection Lifecycle
Der Client baut nach erfolgreicher Authentifizierung eine Verbindung auf.
```text
Login
→ Character laden
→ WebSocket /events verbinden
```
Bei Logout:
```text
WebSocket trennen
```
Bei Verbindungsverlust:
```text
Reconnect versuchen
```
Der WebSocket darf niemals Voraussetzung dafür sein, dass der persistente Spielzustand korrekt bleibt.
---
# 10. Reconnect-Prinzip
Ein WebSocket ist ein Benachrichtigungskanal, nicht die Source of Truth.
Wenn der Client die Verbindung verliert:
```text
WebSocket disconnected
```
läuft das Spiel serverseitig weiter.
Nach Wiederherstellung:
```text
WebSocket reconnect
→ aktuellen Zustand per REST synchronisieren
```
Beispielsweise:
```text
GET /api/characters/me
GET /api/combats/:id
```
Der Client darf niemals annehmen, dass er während des Disconnects keine Events verpasst hat.
---
# 11. Event Envelope
Alle Events verwenden eine gemeinsame Grundstruktur.
Beispiel:
```ts
export interface GameEvent<TPayload = unknown> {
id: string;
sequence?: number;
type: GameEventType;
occurredAt: string;
payload: TPayload;
}
```
Beispiel:
```json
{
"id": "event-uuid",
"sequence": 42,
"type": "combat.action.resolved",
"occurredAt": "2026-08-21T15:00:00.000Z",
"payload": {
"combatId": "combat-uuid"
}
}
```
---
# 12. Event Naming
Event-Namen verwenden:
```text
domain.entity-or-action.event
```
Für Slice 0.13:
```text
combat.action.resolved
combat.turn.changed
combat.finished
combat.state.changed
```
Später möglich:
```text
character.vitals.changed
character.stats.changed
party.member.joined
party.member.left
quest.updated
location.player.joined
location.player.left
```
---
# 13. Keine technischen Event-Namen
Nicht:
```text
UPDATE_COMBAT
REFRESH_UI
DB_CHANGED
SYNC_PLAYER
```
Events beschreiben fachlich, was passiert ist.
Gut:
```text
combat.turn.changed
```
Schlecht:
```text
refreshCombatScreen
```
---
# 14. Rooms / Channels
Die zentrale WebSocket-Verbindung verwendet serverseitige Rooms.
Vorgesehene Room-Typen:
```text
user:<userId>
character:<characterId>
combat:<combatId>
```
Später:
```text
party:<partyId>
location:<locationId>
guild:<guildId>
```
---
# 15. User Room
Jeder eingeloggte Client wird automatisch Mitglied von:
```text
user:<userId>
```
Dieser Room ist geeignet für:
- globale persönliche Benachrichtigungen
- Account-bezogene Ereignisse
- spätere systemweite Hinweise
---
# 16. Character Room
Der Client tritt zusätzlich bei:
```text
character:<characterId>
```
Geeignet für:
- Charakteränderungen
- Statusänderungen
- später Buffs
- später Ruf
- später externe Heilung
---
# 17. Combat Room
Wenn ein Charakter einen aktiven Kampf besitzt:
```text
combat:<combatId>
```
Der Server fügt den Client diesem Room hinzu.
Beim Verlassen oder Ende des Kampfes wird die Subscription entfernt.
Später können mehrere Spieler gleichzeitig Mitglied desselben Combat Rooms sein.
---
# 18. Kein frei wählbares Room Joining
Der Client darf nicht einfach senden:
```text
join combat:xyz
```
und dadurch beliebige Kämpfe beobachten.
Der Server validiert immer:
```text
Ist dieser Charakter Teilnehmer dieses Kampfes?
```
Erst danach darf der Socket dem Room beitreten.
---
# 19. Game Events Backend-Struktur
Vorgesehene Struktur:
```text
apps/api/src/events/
├── events.module.ts
├── events.gateway.ts
├── game-event.types.ts
├── game-event-publisher.service.ts
├── event-room.service.ts
└── events.gateway.spec.ts
```
Optional fachlich getrennt:
```text
apps/api/src/combat/
└── combat-event.publisher.ts
```
---
# 20. `EventsGateway`
Verantwortlich für:
- WebSocket-Verbindungen
- Authentifizierung
- Disconnect
- Reconnect-Unterstützung
- Room-Mitgliedschaften
- Senden von Events
Nicht verantwortlich für:
- Combat-Regeln
- Schadensberechnung
- Charakterwerte
- Loot
- Questlogik
---
# 21. `GameEventPublisher`
Der Domain-Code soll nicht direkt mit dem WebSocket-Gateway kommunizieren.
Nicht:
```ts
this.eventsGateway.server
.to(room)
.emit(...);
```
innerhalb des CombatService.
Stattdessen:
```ts
this.gameEventPublisher.publishToCombat(
combatId,
event,
);
```
Dadurch bleibt der Transport austauschbar und die Domain-Logik kennt keine Socket.IO-Details.
---
# 22. Beispiel Publisher Interface
Konzeptionell:
```ts
export interface GameEventPublisher {
publishToUser(
userId: string,
event: GameEvent,
): void;
publishToCharacter(
characterId: string,
event: GameEvent,
): void;
publishToCombat(
combatId: string,
event: GameEvent,
): void;
}
```
---
# 23. Combat als erster Use Case
Slice 0.13 verwendet Combat als ersten realen Event-Produzenten.
Der bestehende Combat-Flow bleibt:
```text
Client
→ POST Combat Action
→ Server berechnet Resultat
→ Server persistiert Resultat
→ Response
```
Zusätzlich:
```text
→ Server veröffentlicht Combat Events
→ WebSocket liefert sie an Combat Room
```
---
# 24. `combat.action.resolved`
Wird veröffentlicht, wenn eine Combat Action serverseitig vollständig verarbeitet wurde.
Beispiel:
```json
{
"type": "combat.action.resolved",
"payload": {
"combatId": "abc",
"round": 4,
"events": [
{
"sequence": 21,
"type": "PLAYER_ATTACK",
"actorId": "player",
"targetId": "monster",
"amount": 20
}
]
}
}
```
Der Client verwendet dieses Event für:
- Animationen
- Damage Numbers
- Combat Log
- UI-State
---
# 25. `combat.turn.changed`
Wird veröffentlicht, sobald sich der aktive Actor ändert.
Payload:
```ts
interface CombatTurnChangedPayload {
combatId: string;
actorId: string;
startedAt: string;
expiresAt?: string;
}
```
Für den aktuellen 1-vs-1-Combat kann dieses Event zunächst nur begrenzt genutzt werden.
Die Struktur wird jedoch bereits für spätere Mehrteilnehmer-Kämpfe vorbereitet.
---
# 26. `combat.finished`
Wird bei Sieg, Niederlage oder später Flucht veröffentlicht.
Beispiel:
```json
{
"type": "combat.finished",
"payload": {
"combatId": "abc",
"result": "WON"
}
}
```
Später können weitere Informationen ergänzt werden:
```text
loot
reputation
questProgress
```
Diese Daten müssen jedoch weiterhin serverseitig autoritativ sein.
---
# 27. `combat.state.changed`
Optionales allgemeines Sync-Event.
Es darf verwendet werden, wenn der Client wissen soll:
> Der Combat-State hat sich geändert; lade bei Bedarf den aktuellen Zustand neu.
Beispiel:
```json
{
"type": "combat.state.changed",
"payload": {
"combatId": "abc",
"revision": 17
}
}
```
Es soll nicht jede spezialisierte Combat-Nachricht ersetzen.
---
# 28. HTTP Response und WebSocket Event
Für die Aktion des lokalen Spielers darf die HTTP Response weiterhin das Resultat enthalten.
Das erzeugt bewusst mögliche doppelte Information:
```text
HTTP Response
+
WebSocket Broadcast
```
Der Client muss damit umgehen können.
Deshalb brauchen Events stabile IDs oder Sequenzen.
---
# 29. Event-Deduplizierung
Der Client darf dasselbe Combat Event nicht zweimal animieren.
CombatEvents besitzen bereits beziehungsweise erhalten eine stabile Reihenfolge.
Beispiel:
```text
combatId
sequence
```
Client speichert:
```text
lastProcessedSequence
```
Wenn:
```text
sequence <= lastProcessedSequence
```
wird das Event nicht erneut abgespielt.
---
# 30. Event-Reihenfolge
Innerhalb eines Combat Rooms muss die Reihenfolge serverseitig eindeutig sein.
Beispiel:
```text
41 PLAYER_ATTACK
42 DAMAGE_APPLIED
43 MONSTER_ATTACK
44 DAMAGE_APPLIED
45 TURN_CHANGED
```
Der Client spielt diese Reihenfolge ab.
Nicht die Empfangszeit entscheidet über die fachliche Reihenfolge.
---
# 31. Persistierte CombatEvents bleiben Source of Truth
WebSocket Events ersetzen nicht die persistierten `CombatEvent`-Datensätze.
Die Datenbank bleibt maßgeblich.
Dadurch werden später möglich:
- Reconnect
- Combat Log
- Debugging
- Replay
- nachträgliches Nachladen verpasster Events
---
# 32. Resync nach Event-Lücke
Wenn ein Client erkennt:
```text
letzte bekannte Sequence = 40
neues Event = 44
```
existiert eine Lücke.
Dann darf der Client nicht raten.
Er synchronisiert:
```text
GET /api/combats/:id
```
oder später:
```text
GET /api/combats/:id/events?after=40
```
Der konkrete Events-After-Endpunkt muss in 0.13 noch nicht zwingend implementiert werden.
---
# 33. Frontend-Struktur
Vorgeschlagene Struktur:
```text
apps/web/src/app/core/events/
├── game-events.service.ts
├── game-events.models.ts
├── game-events.store.ts
└── game-events.service.spec.ts
```
---
# 34. `GameEventsService`
Verantwortlich für:
- Verbindung herstellen
- Reconnect
- eingehende Events empfangen
- Event-Typen verteilen
- Lifecycle verwalten
Nicht verantwortlich für:
- Combat-State
- Character-State
- Quest-State
---
# 35. Event-Verteilung im Frontend
Der zentrale Event-Service verteilt Events fachlich.
Konzeptionell:
```ts
combatEvents$
characterEvents$
partyEvents$
```
oder mit Angular Signals:
```ts
combatEvent
characterEvent
```
Feature Stores abonnieren nur relevante Eventtypen.
---
# 36. CombatStore Integration
Der CombatStore reagiert auf:
```text
combat.action.resolved
combat.turn.changed
combat.finished
```
Der Store darf daraus:
- Events in Animationsqueue einreihen
- Aktionen aktivieren/deaktivieren
- Combat-State aktualisieren
- bei Unsicherheit REST-Resync auslösen
---
# 37. WebSocket Event ≠ Animation
Das Backend definiert fachliche Events.
Nicht:
```text
PLAY_SWORD_ANIMATION
WAIT_800_MS
SHOW_RED_NUMBER
```
Sondern:
```text
ATTACK
DAMAGE
HEAL
STATUS_APPLIED
TURN_CHANGED
```
Der Client entscheidet, wie diese visuell dargestellt werden.
Damit bleibt die Trennung erhalten:
> Server entscheidet, was passiert. Client entscheidet, wie es aussieht.
---
# 38. Keine WebSocket-HP-Ticks
Das bestehende HP-Regenerationsmodell bleibt unverändert.
Weiterhin:
```text
currentHp
hpRegenSince
hpRegenPerSecond
```
Der Client berechnet die sichtbare Regeneration lokal.
Nicht implementieren:
```text
character.hp.changed 71
character.hp.changed 72
character.hp.changed 73
```
pro Sekunde.
---
# 39. Spätere Character Events
Die Architektur soll jedoch ermöglichen:
```text
character.vitals.changed
```
wenn eine **diskrete externe Veränderung** eintritt.
Beispiele:
- ein anderer Spieler heilt
- ein Gruppenbuff verändert Max HP
- ein Debuff verursacht Schaden
- eine externe Spielaktion verändert HP
Beispiel:
```json
{
"type": "character.vitals.changed",
"payload": {
"characterId": "abc",
"currentHp": 84,
"hpRegenSince": "2026-08-21T15:10:20.000Z"
}
}
```
Danach läuft die bestehende lokale Regeneration weiter.
Dieses Event muss in Slice 0.13 noch nicht produktiv verwendet werden.
---
# 40. Keine globale Server-Tick-Schleife
Slice 0.13 führt ausdrücklich keinen zentralen:
```text
10 Hz
20 Hz
60 Hz
```
Game Loop ein.
Ashen Realms bleibt:
```text
event-driven
+
turn-based
+
timestamp-based
```
Server-Ticks werden erst eingeführt, wenn ein konkretes zukünftiges Feature sie tatsächlich benötigt.
---
# 41. Vorbereitung für verzögerte NPC-Aktionen
Die Event-Infrastruktur muss ermöglichen, dass später folgendes passiert:
```text
Player Action
→ Commit
→ Combat Event
→ NPC Turn geplant
→ HTTP Request ist längst beendet
→ NPC Action wird ausgeführt
→ Commit
→ WebSocket Combat Event
```
Der Client muss dadurch nicht mehr selbst warten und anschließend eine NPC-Aktion simulieren.
Die tatsächliche serverseitige NPC-Turn-Scheduling-Logik ist nicht Teil von 0.13.
---
# 42. Keine `sleep()`-Requests
Nicht implementieren:
```ts
await sleep(1000);
performNpcAttack();
return response;
```
HTTP Requests dürfen nicht künstlich offen gehalten werden, um Kampftiming zu simulieren.
Spätere AI-Turns müssen unabhängig vom ursprünglichen Request verarbeitet werden können.
---
# 43. Vorbereitung auf Combat Participants
0.13 muss noch keine vollständige `CombatParticipant`-Migration durchführen, darf die Event Contracts aber nicht hart auf:
```text
PLAYER
MONSTER
```
begrenzen.
Events sollten allgemein mit:
```text
actorId
targetId
```
arbeiten.
Nicht:
```text
playerDamage
monsterDamage
```
Dadurch können spätere Teilnehmer sein:
```text
PLAYER
NPC
PET
COMPANION
BOSS
```
---
# 44. Beispiel zukünftiger Kampf
Die 0.13-Architektur soll später ohne neuen Transportmechanismus ermöglichen:
```text
TEAM A
- Player A
- Player B
- Pet
TEAM B
- Bandit
- Wolf
- Bandit
```
Ablauf:
```text
Player A handelt
→ Event
Wolf handelt
→ Event
Player B ist dran
→ Event
Player B Client erhält:
combat.turn.changed
```
---
# 45. Fehlerbehandlung
WebSocket-Fehler dürfen keinen falschen Spielzustand erzeugen.
Bei:
```text
Disconnect
invalid event
sequence gap
parse error
```
gilt:
```text
UI als unsynchronisiert markieren
→ REST Resync
```
Nicht:
```text
lokal weitersimulieren
```
---
# 46. Client-Verbindungsstatus
Optional sichtbar oder intern:
```ts
type EventConnectionState =
| 'DISCONNECTED'
| 'CONNECTING'
| 'CONNECTED'
| 'RECONNECTING';
```
Bei kurzfristigem Disconnect soll nicht sofort eine störende Fehlermeldung erscheinen.
Erst wenn Realtime tatsächlich für eine aktive Funktion nötig ist, muss der Zustand prominent dargestellt werden.
---
# 47. Security
Der Server muss prüfen:
- gültige Authentifizierung
- Socket gehört zum User
- Character gehört zum User
- Combat gehört zum Character
- Room-Mitgliedschaft ist erlaubt
Der Client darf:
- keine fremden Combat Rooms abonnieren
- keine fremden Character Rooms abonnieren
- keine Server Events fälschen
---
# 48. Rate Limits
Für 0.13 ist kein komplexes WebSocket Rate Limiting notwendig.
Dennoch gilt:
Client-Nachrichten über den Socket sollten möglichst minimal bleiben.
Da Commands weiterhin über REST laufen, ist der WebSocket in 0.13 fast ausschließlich:
```text
Server → Client
```
---
# 49. Warum trotzdem WebSocket statt SSE
Obwohl Slice 0.13 hauptsächlich Server-Push benötigt, wird direkt WebSocket verwendet.
Grund:
Spätere Systeme benötigen voraussichtlich bidirektionale Echtzeitkommunikation:
- Multiplayer Combat
- Parties
- Chat
- Presence
- Social Systems
Dadurch wird vermieden:
```text
REST
+ SSE
+ später zusätzlich WebSocket
```
Ziel bleibt:
```text
REST
+
WebSocket
```
---
# 50. Shared Contracts
Cross-Boundary Contracts gehören nach:
```text
packages/shared
```
Geeignet:
```ts
GameEvent
GameEventType
CombatActionResolvedPayload
CombatTurnChangedPayload
CombatFinishedPayload
```
Nicht dort ablegen:
- NestJS Gateway
- Socket.IO Server
- Angular Services
- TypeORM Entities
---
# 51. Vorgeschlagene Dateien Backend
```text
packages/shared/src/events/
├── game-event.ts
├── game-event-type.ts
└── combat-events.ts
apps/api/src/events/
├── events.module.ts
├── events.gateway.ts
├── event-room.service.ts
├── game-event-publisher.service.ts
├── events.gateway.spec.ts
└── game-event-publisher.service.spec.ts
```
Zusätzlich Änderungen in:
```text
apps/api/src/combat/
```
---
# 52. Vorgeschlagene Dateien Frontend
```text
apps/web/src/app/core/events/
├── game-events.service.ts
├── game-events.service.spec.ts
└── game-events.models.ts
```
Änderungen:
```text
CombatStore
App initialization
Authentication lifecycle
```
---
# 53. Implementierungsreihenfolge
## Task 1 Shared Event Contracts
Implementieren:
```text
GameEvent
GameEventType
Combat Event Payloads
```
Tests:
- TypeScript compile
- Contract consistency
---
## Task 2 WebSocket Gateway
Implementieren:
```text
/events
Authentication
Connect
Disconnect
```
Tests:
- unauthenticated rejected
- authenticated accepted
---
## Task 3 Room Infrastructure
Implementieren:
```text
user room
character room
combat room
```
Tests:
- nur berechtigte Membership
- kein fremder Combat Room
---
## Task 4 GameEventPublisher
Domain-unabhängige Publish API implementieren.
Tests:
```text
publishToUser
publishToCharacter
publishToCombat
```
---
## Task 5 Combat Integration
Nach erfolgreichem Combat Commit:
```text
combat.action.resolved
combat.turn.changed
combat.finished
```
veröffentlichen.
---
## Task 6 Frontend GameEventsService
Implementieren:
```text
connect
disconnect
reconnect
event parsing
```
---
## Task 7 CombatStore Integration
CombatEvents empfangen und in bestehende Combat-Darstellung integrieren.
Bestehende REST-Funktionalität muss weiterhin funktionieren.
---
## Task 8 Reconnect & Resync
Bei Reconnect:
```text
Character neu laden
aktiven Combat prüfen
Combat State neu laden
Rooms erneut herstellen
```
---
# 54. Testing Backend
Mindestens folgende Tests:
### Gateway
- nicht authentifizierte Verbindung wird abgelehnt
- authentifizierte Verbindung wird akzeptiert
- User Room wird automatisch verbunden
- Character Room wird korrekt verbunden
- fremder Combat Room wird abgelehnt
### Publisher
- Combat Event geht nur an Combat Room
- Character Event geht nur an Character Room
### Combat
- Action wird persistiert
- erst danach Event veröffentlicht
- Kampfende veröffentlicht `combat.finished`
- Event-Sequenz entspricht persistierter Reihenfolge
---
# 55. Testing Frontend
Mindestens:
- Verbindung wird nach Login aufgebaut
- Verbindung wird bei Logout geschlossen
- Reconnect wird versucht
- unbekannte Event-Typen crashen den Client nicht
- Combat Event erreicht CombatStore
- doppelte Sequence wird ignoriert
- Sequence Gap löst Resync aus
- Disconnect zerstört lokalen Spielzustand nicht
---
# 56. End-to-End-Test
Minimaler E2E-Flow:
```text
Client verbindet /events
→ Hunt starten
→ Combat starten
→ Combat Room betreten
→ ATTACK per REST senden
Server:
→ Action validieren
→ Combat berechnen
→ Combat speichern
→ Transaction committen
→ combat.action.resolved senden
Client:
→ Event empfangen
→ Combat UI aktualisieren
→ Animation ausführen
```
Bei letztem Treffer:
```text
→ Combat WON
→ combat.finished
→ Combat Room verlassen
```
---
# 57. Migration Requirement
Slice 0.13 sollte möglichst **keine neue Datenbankmigration nur für WebSockets** benötigen.
Persistierte CombatEvents bleiben bestehen.
Falls für Event-Reihenfolge bereits ein geeignetes Feld existiert, wird dieses weiterverwendet.
Nur wenn eine stabile Sequenz heute technisch nicht vorhanden ist, darf eine kleine CombatEvent-Sequenzmigration ergänzt werden.
---
# 58. Performance-Grundsatz
Keine globalen Broadcasts.
Nicht:
```text
send to all connected clients
```
Sondern:
```text
send to combat room
send to character room
send to user room
```
Damit wächst das System später mit der Anzahl aktiver Spieler besser mit.
---
# 59. Observability
Für Development Logging vorsehen:
```text
socket connected
socket disconnected
room joined
room left
event published
```
Keine vollständigen sensiblen Payloads ungefiltert loggen.
Besonders hilfreich:
```text
event type
combatId
room
sequence
```
---
# 60. Definition of Done
Slice 0.13 ist abgeschlossen, wenn:
- ein authentifizierter zentraler `/events` WebSocket existiert
- der Client genau eine zentrale Verbindung verwendet
- Reconnect funktioniert
- User-, Character- und Combat-Room-Konzept existiert
- Clients keine fremden Rooms abonnieren können
- ein gemeinsames `GameEvent`-Format existiert
- Combat Event Contracts in `packages/shared` liegen
- Combat-Aktionen weiterhin per REST ausgelöst werden
- Combat-Änderungen zusätzlich per WebSocket veröffentlicht werden
- Events erst nach erfolgreichem DB Commit gesendet werden
- Combat Events geordnet und deduplizierbar sind
- der Frontend CombatStore Events empfangen kann
- Disconnect und Reconnect keinen Spielzustand beschädigen
- HP-Regeneration unverändert timestamp-basiert funktioniert
- keine globale Server-Tick-Schleife eingeführt wurde
- bestehende Slice-0.12-Funktionalität weiterhin funktioniert
- Backend- und Frontendtests grün sind
---
# 61. Architektur nach Slice 0.13
Nach erfolgreicher Implementierung gilt:
```text
Ashen Realms Client
/ \
/ \
REST Commands WebSocket Events
| ^
v |
NestJS API EventsGateway
| ^
v |
Domain Services ---- EventPublisher
|
v
PostgreSQL
```
Grundregel:
> **REST: Ich möchte etwas tun.**
> **WebSocket: Etwas ist passiert.**
> **Zeitstempel: Etwas verändert sich vorhersehbar mit der Zeit.**
---
# 62. Vorbereitung für Slice 0.14+
Die Architektur soll insbesondere folgende nächsten Schritte ermöglichen:
### Slice 0.14
Combat Participants / mehrere Kampfteilnehmer
### Slice 0.15
Erster Mehrgegnerkampf
Beispiel:
```text
1 Spieler
vs.
3 NPCs
```
### Später
```text
2 Spieler
vs.
3 NPCs
```
sowie:
```text
Pets
NPC-Begleiter
Party Combat
PvP
```
Diese Systeme dürfen denselben:
```text
/events WebSocket
```
weiterverwenden.
Es wird kein separates Realtime-System für jeden neuen Featurebereich aufgebaut.
---
# 63. Leitentscheidung
Die wichtigste Entscheidung von Slice 0.13 lautet:
> **Ashen Realms erhält keine klassische permanente Realtime-Simulation, sondern eine serverautoritative, ereignisgetriebene Realtime-Schicht.**
Damit bleibt das Spiel technisch passend zu seiner Identität:
- browserbasiert
- rundenbasiert
- persistent
- serverautoritativ
- später multiplayerfähig
- ohne unnötigen permanenten Game-Server-Tick