# Ashen Realms – Technical Foundation & Monorepo Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Eine schlanke, produktionsfähige technische Basis für Ashen Realms erstellen, in der Angular und NestJS in einem npm-Workspace-Monorepo entwickelt und als ein einziges Docker-Image ausgeliefert werden, während PostgreSQL als separater persistenter Datendienst läuft.

**Architecture:** Ashen Realms wird als modularer Monolith umgesetzt. Angular ist die browserseitige SPA, NestJS ist der einzige Runtime-Prozess im Produktionscontainer und stellt sowohl `/api/*` als auch die gebauten Angular-Dateien bereit. PostgreSQL wird über TypeORM angesprochen; Spiellogik ist vollständig serverautoritativ.

**Tech Stack:** Node.js 24 LTS, npm Workspaces, Angular 22.x, NestJS 11.x, TypeScript, TypeORM 1.x, PostgreSQL, SCSS, Docker Multi-Stage Build, Jest/Vitest je nach generiertem Angular-Setup, Supertest für API-Tests.

**Spec:** Diese Planung implementiert die bestehenden Projektunterlagen:
- `docs/design-manifest.md`
- `docs/vertical-slice-world-content-design.md`
- `docs/balancing-items-loot-design.md`
- `docs/ui-visual-design-specification.md`

## Global Constraints

- Kein Nx, Turborepo oder vergleichbares Monorepo-Framework.
- Ein npm-Workspace-Root verwaltet `apps/*` und `packages/*`.
- Produktionsdeployment besteht aus genau einem Ashen-Realms-Anwendungscontainer.
- PostgreSQL läuft außerhalb des Anwendungscontainers.
- NestJS ist der einzige Runtime-Prozess im Anwendungscontainer.
- Angular wird im Produktionsbuild statisch durch NestJS ausgeliefert.
- Alle API-Routen verwenden den Prefix `/api`.
- Spiellogik ist serverautoritativ.
- TypeORM verwendet Migrationen; `synchronize: true` ist in Produktion verboten.
- Content-Definitionen und Player-State bleiben fachlich getrennt.
- Keine Microservices, kein Redis, Kafka, RabbitMQ, GraphQL, CQRS oder Event Sourcing für V1.
- Assets liegen für den ersten Vertical Slice lokal im Web-Bundle.
- Desktop ist die primäre Zielplattform für den ersten UI-Prototyp.
- Der erste technische End-to-End-Loop lautet: Charakter → Welt → Reise → Jagd → Kampf → Loot → Inventar → Equipment.

---

# 1. Zielarchitektur

```text
Browser
   |
   | HTTPS
   v
+--------------------------------------------------+
| Ashen Realms Application Container              |
|                                                  |
|  NestJS                                         |
|  +-- /api/*            REST API                 |
|  +-- /*                Angular SPA              |
|  +-- Auth                                      |
|  +-- Characters                                |
|  +-- World / Travel                            |
|  +-- Hunting                                   |
|  +-- Combat                                    |
|  +-- Inventory / Equipment                     |
|  +-- Loot                                      |
|                                                  |
+-------------------------+------------------------+
                          |
                          | PostgreSQL protocol
                          v
                 +------------------+
                 | PostgreSQL       |
                 | persistent data  |
                 +------------------+
```

## Entwicklungsmodus

```text
localhost:4200  Angular Dev Server
       |
       | /api proxy
       v
localhost:3000  NestJS
       |
       v
localhost:5432  PostgreSQL
```

Produktiv existiert kein separater Angular-Server.

---

# 2. Repository-Struktur

```text
ashen-realms/
├── apps/
│   ├── web/
│   │   ├── public/
│   │   │   └── assets/
│   │   │       ├── characters/
│   │   │       ├── items/
│   │   │       ├── locations/
│   │   │       ├── monsters/
│   │   │       └── ui/
│   │   └── src/
│   │       └── app/
│   │           ├── core/
│   │           ├── layout/
│   │           ├── features/
│   │           │   ├── world/
│   │           │   ├── hunting/
│   │           │   ├── combat/
│   │           │   ├── inventory/
│   │           │   └── character/
│   │           └── shared/
│   │
│   └── api/
│       ├── src/
│       │   ├── auth/
│       │   ├── users/
│       │   ├── characters/
│       │   ├── world/
│       │   ├── travel/
│       │   ├── hunting/
│       │   ├── combat/
│       │   ├── monsters/
│       │   ├── items/
│       │   ├── inventory/
│       │   ├── equipment/
│       │   ├── loot/
│       │   ├── database/
│       │   ├── config/
│       │   ├── app.module.ts
│       │   └── main.ts
│       └── test/
│
├── packages/
│   ├── shared/
│   │   └── src/
│   │       ├── enums/
│   │       ├── contracts/
│   │       └── index.ts
│   │
│   └── game-content/
│       └── src/
│           ├── schemas/
│           ├── enums/
│           └── index.ts
│
├── docs/
│   ├── design-manifest.md
│   ├── vertical-slice-world-content-design.md
│   ├── balancing-items-loot-design.md
│   ├── ui-visual-design-specification.md
│   └── superpowers/
│       └── plans/
│
├── .dockerignore
├── .env.example
├── .gitignore
├── Dockerfile
├── package.json
├── package-lock.json
├── tsconfig.base.json
└── README.md
```

---

# 3. Verantwortlichkeiten der Workspaces

## `apps/web`

Verantwortlich für:

- Darstellung des Spielzustands
- Eingaben des Spielers
- Navigation
- UI-State
- Animationen
- Aufruf der REST-API
- Countdown-Darstellung für Reisen
- Darstellung serverseitig berechneter Kampfereignisse

Nicht verantwortlich für:

- Schadensberechnung
- Loot-Rolls
- Reiseabschluss
- seltene Begegnungswürfe
- XP-Vergabe
- Itemwerte

---

## `apps/api`

Verantwortlich für:

- Authentifizierung
- Persistenz
- Charakterzustand
- Reisen
- Jagdergebnisse
- Kampfregeln
- Loot
- Inventar
- Equipment
- XP
- serverautoritative Validierung

---

## `packages/shared`

Nur für echte Cross-Boundary-Verträge.

Geeignet:

```ts
export enum DangerRating {
  WEAK = 'WEAK',
  MATCH = 'MATCH',
  STRONG = 'STRONG',
  VERY_DANGEROUS = 'VERY_DANGEROUS',
  DEADLY = 'DEADLY',
}
```

```ts
export interface CombatEventDto {
  type: string;
  source: 'PLAYER' | 'MONSTER';
  target: 'PLAYER' | 'MONSTER';
  amount?: number;
}
```

Nicht geeignet:

- TypeORM Entities
- NestJS Services
- Angular Components
- Domain-Implementierungsdetails

---

## `packages/game-content`

V1 enthält hauptsächlich gemeinsame Content-Schemas und Enums.

Die tatsächlichen persistierten Content-Daten liegen in PostgreSQL und werden über Seeds/Migrationen angelegt.

Beispiel:

```ts
export enum ItemRarity {
  COMMON = 'COMMON',
  RARE = 'RARE',
  EPIC = 'EPIC',
}
```

Später kann dieses Package Validierungsschemas für ein CMS enthalten.

---

# 4. API-Konventionen

Alle Endpunkte beginnen mit:

```text
/api
```

Beispiele:

```text
POST /api/auth/register
POST /api/auth/login

GET  /api/characters/me

GET  /api/world/current-location
POST /api/travel
GET  /api/travel/current

POST /api/hunts
POST /api/hunt-encounters/:id/attack

POST /api/combats/:id/actions

GET  /api/inventory
GET  /api/equipment
POST /api/equipment
DELETE /api/equipment/:slot
```

Fehler verwenden ein konsistentes Format:

```json
{
  "statusCode": 400,
  "code": "INVALID_TRAVEL_TARGET",
  "message": "The selected location is not connected to the current location."
}
```

---

# 5. TypeORM-Konzept

## Data Source

Datei:

```text
apps/api/src/database/data-source.ts
```

Konzeptionell:

```ts
import 'reflect-metadata';
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  entities: ['dist/**/*.entity.js'],
  migrations: ['dist/database/migrations/*.js'],
  synchronize: false,
});
```

Für Development/CLI muss ein TypeScript-kompatibler Data-Source-Pfad konfiguriert werden.

---

## Migrationen

Ablage:

```text
apps/api/src/database/migrations/
```

Workflow:

```text
Entity ändern
→ Migration generieren
→ SQL prüfen
→ Migration ausführen
→ Tests
```

Produktionsstart führt Migrationen kontrolliert vor dem App-Start aus oder über einen expliziten Deployment-Schritt.

Keine automatische Schema-Synchronisation.

---

# 6. V1-Datenmodell

## Content

```text
LocationDefinition
LocationConnection
MonsterDefinition
LocationMonster
AbilityDefinition
ItemDefinition
LootTable
LootTableEntry
```

## Player State

```text
User
Character
CharacterItem
CharacterEquipment
Travel
Hunt
HuntEncounter
Combat
CombatEvent
```

---

# 7. Kern-Relationen

```text
User
└── Character[]

Character
├── currentLocation -> LocationDefinition
├── CharacterItem[]
├── CharacterEquipment[]
├── Travel[]
├── Hunt[]
└── Combat[]

LocationDefinition
├── LocationConnection[]
└── LocationMonster[]

MonsterDefinition
├── AbilityDefinition[]
└── LootTable

ItemDefinition
└── CharacterItem[]
```

---

# 8. Zentrale Services

## `CharacterStatsService`

Einzige Quelle für effektive Charakterwerte.

```ts
export interface EffectiveCharacterStats {
  maxHp: number;
  currentHp: number;
  attack: number;
  weaponDamage: number;
  armor: number;
  combatPower: number;
}
```

Berechnet aus:

```text
Character Base Stats
+ Equipment
+ ItemDefinition
+ später Buffs / Sets
```

---

## `TravelService`

Verantwortlich für:

```ts
startTravel(characterId: string, targetLocationId: string): Promise<Travel>
getCurrentTravel(characterId: string): Promise<Travel | null>
completeTravelIfDue(characterId: string): Promise<Travel | null>
```

---

## `HuntingService`

```ts
startHunt(characterId: string): Promise<HuntResultDto>
attackEncounter(characterId: string, encounterId: string): Promise<Combat>
```

---

## `CombatEngineService`

Framework-unabhängige Kampflogik.

```ts
resolveAction(
  state: CombatEngineState,
  action: CombatActionInput,
): CombatEngineResult
```

Wichtig:

Der Service soll möglichst ohne Datenbankzugriff testbar sein.

---

## `CombatService`

Orchestriert:

```text
DB-Zustand laden
→ CombatEngineService aufrufen
→ Events persistieren
→ Sieg/Niederlage prüfen
→ Loot auslösen
→ Transaktion committen
```

---

## `LootService`

```ts
rollLoot(monsterDefinitionId: string): Promise<LootResult>
grantLoot(characterId: string, result: LootResult): Promise<void>
```

---

# 9. Umgebungsvariablen

`.env.example`

```dotenv
NODE_ENV=development
PORT=3000

DATABASE_URL=postgresql://ashen:ashen@localhost:5432/ashen_realms

JWT_ACCESS_SECRET=change-me
JWT_REFRESH_SECRET=change-me-too
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=30d
```

Keine Secrets im Repository.

---

# 10. Root npm Workspaces

Root `package.json`:

```json
{
  "name": "ashen-realms",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "dev:web": "npm run start --workspace=@ashen-realms/web",
    "dev:api": "npm run start:dev --workspace=@ashen-realms/api",
    "build:web": "npm run build --workspace=@ashen-realms/web",
    "build:api": "npm run build --workspace=@ashen-realms/api",
    "build": "npm run build:web && npm run build:api",
    "test": "npm run test --workspaces --if-present",
    "lint": "npm run lint --workspaces --if-present"
  }
}
```

Workspace-Namen:

```text
@ashen-realms/web
@ashen-realms/api
@ashen-realms/shared
@ashen-realms/game-content
```

---

# 11. Angular Development Proxy

`apps/web/proxy.conf.json`

```json
{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false,
    "changeOrigin": true
  }
}
```

Angular-Dev-Start verwendet diese Proxy-Konfiguration.

Frontend-Code ruft immer relative URLs auf:

```ts
this.http.get('/api/characters/me');
```

Nicht:

```ts
this.http.get('http://localhost:3000/api/characters/me');
```

Dadurch funktioniert derselbe Code in Development und Produktion.

---

# 12. Angular App Shell

V1-Komponenten:

```text
AppShellComponent
TopBarComponent
SideNavigationComponent
GameFooterComponent
ContextPanelComponent
```

Feature-Routen:

```text
/world
/hunt
/combat/:combatId
/inventory
/character
```

Die visuelle Umsetzung folgt `docs/ui-visual-design-specification.md`.

---

# 13. Produktionsauslieferung von Angular

Angular wird zuerst gebaut.

Das erzeugte Browser-Bundle wird anschließend in einen Pfad kopiert, den NestJS statisch ausliefert.

Ziel im Runtime-Image:

```text
/app/
├── api-dist/
├── web-dist/
└── node_modules/
```

NestJS liefert:

```text
/api/*  → Controller
/*      → Angular Static Files
```

Nicht gefundene Nicht-API-Routen fallen auf:

```text
web-dist/index.html
```

Damit funktionieren direkte Aufrufe wie:

```text
/world
/inventory
/combat/123
```

---

# 14. Docker-Ziel

Ein Image, ein Prozess.

## Multi-Stage-Konzept

```dockerfile
FROM node:24-alpine AS build

WORKDIR /app

COPY package*.json ./
COPY apps ./apps
COPY packages ./packages
COPY tsconfig.base.json ./

RUN npm ci
RUN npm run build


FROM node:24-alpine AS runtime

ENV NODE_ENV=production
WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY --from=build /app/apps/api/dist ./api-dist
COPY --from=build /app/apps/web/dist ./web-build

EXPOSE 3000

CMD ["node", "api-dist/main.js"]
```

Der konkrete Angular-Ausgabeunterordner muss beim tatsächlichen Bootstrap einmal geprüft und im Dockerfile entsprechend gesetzt werden.

---

# 15. Container-Prinzipien

Der App-Container ist zustandslos.

Nicht im Container persistieren:

- PostgreSQL-Daten
- Uploads
- Sessions als lokale Dateien
- User-Content

Ein Container-Neustart darf keine Spieldaten verlieren.

---

# 16. Health Endpoint

V1:

```text
GET /api/health
```

Response:

```json
{
  "status": "ok"
}
```

Später kann DB-Konnektivität ergänzt werden.

Dieser Endpoint wird auch für Docker-/Deployment-Healthchecks verwendet.

---

# 17. Seed-Strategie

V1 benötigt reproduzierbare Content-Daten.

Ablage:

```text
apps/api/src/database/seeds/
```

Erste Seeds:

```text
locations.seed.ts
location-connections.seed.ts
items.seed.ts
monsters.seed.ts
location-monsters.seed.ts
loot-tables.seed.ts
```

Seeds verwenden stabile `key`-Werte:

```text
ashen-fields
burned-road
dusk-wolf
ashen-blade
```

Interne UUIDs dürfen variieren; fachliche Referenzen verwenden stabile Keys.

---

# 18. Teststrategie

## Unit Tests

Besonders wichtig:

```text
CharacterStatsService
CombatEngineService
LootService
TravelService
HuntingService
```

## API Integration Tests

Mindestens:

```text
GET /api/health
Travel start
Hunt start
Combat action
Inventory read
Equip item
```

## Frontend Tests

Für V1:

- App Shell rendert
- Navigation funktioniert
- World Screen verarbeitet API-State
- Combat Action sendet nur Action-ID und niemals Schadenswerte

---

# 19. Erster End-to-End-Slice

Der erste wirklich spielbare technische Slice enthält nur:

```text
1 Character
2 Locations
1 Connection
2 Monsters
3 Items
1 Hunt
1 Combat Flow
1 Loot Table
Inventory
Equipment
```

Beispiel:

```text
Graufurt Südtor
   |
   v
Verbrannte Straße
   |
   +-- Aschenratte
   +-- Straßenräuber
```

Items:

```text
Abgenutztes Kurzschwert
Räuberklinge
Räuberhaube
```

Damit wird zuerst die Architektur bewiesen, bevor der komplette Content eingepflegt wird.

---

# 20. Nicht im ersten technischen Slice

Noch nicht implementieren:

- Quests
- Gebietswährungen
- Händler
- Sets
- Bosskampf
- Social Features
- Realtime
- Chat
- Gilden
- Auktionshaus
- Crafting
- Admin CMS
- Object Storage
- PvP

---

# 21. Implementierungsreihenfolge

### Task 1: Workspace und Toolchain

**Files:**
- Create: `package.json`
- Create: `tsconfig.base.json`
- Create: `.gitignore`
- Create: `.env.example`
- Create: `README.md`
- Create: `apps/web/*`
- Create: `apps/api/*`
- Create: `packages/shared/*`
- Create: `packages/game-content/*`

**Interfaces:**
- Produces: npm Workspaces `@ashen-realms/web`, `@ashen-realms/api`, `@ashen-realms/shared`, `@ashen-realms/game-content`
- Consumes: none

- [ ] **Step 1: Initialisiere den Root-Workspace**

```bash
mkdir ashen-realms
cd ashen-realms
npm init -y
mkdir -p apps packages docs
```

- [ ] **Step 2: Konfiguriere npm Workspaces**

Root `package.json` auf die Struktur aus Abschnitt 10 setzen.

- [ ] **Step 3: Erzeuge Angular**

```bash
npx @angular/cli@22 new web \
  --directory apps/web \
  --routing \
  --style scss \
  --skip-git \
  --standalone
```

- [ ] **Step 4: Erzeuge NestJS**

```bash
npx @nestjs/cli new apps/api \
  --package-manager npm \
  --skip-git
```

Danach den Paketnamen auf `@ashen-realms/api` setzen.

- [ ] **Step 5: Erzeuge Shared Packages**

```bash
mkdir -p packages/shared/src
mkdir -p packages/game-content/src
```

Minimaler `packages/shared/package.json`:

```json
{
  "name": "@ashen-realms/shared",
  "version": "0.0.1",
  "private": true,
  "main": "src/index.ts",
  "types": "src/index.ts"
}
```

Analog für `@ashen-realms/game-content`.

- [ ] **Step 6: Installiere Root-Abhängigkeiten**

```bash
npm install
```

- [ ] **Step 7: Verifiziere Builds**

```bash
npm run build:web
npm run build:api
```

Expected: beide Builds erfolgreich.

- [ ] **Step 8: Commit**

```bash
git add .
git commit -m "chore: bootstrap angular nest monorepo"
```

---

### Task 2: PostgreSQL und TypeORM Basis

**Files:**
- Create: `apps/api/src/config/database.config.ts`
- Create: `apps/api/src/database/data-source.ts`
- Create: `apps/api/src/database/database.module.ts`
- Modify: `apps/api/src/app.module.ts`
- Modify: `apps/api/package.json`
- Test: `apps/api/src/database/database.module.spec.ts`

**Interfaces:**
- Produces: `AppDataSource`, TypeORM connection through Nest
- Consumes: `DATABASE_URL`

- [ ] **Step 1: Installiere DB-Abhängigkeiten**

```bash
npm install --workspace=@ashen-realms/api typeorm @nestjs/typeorm pg reflect-metadata
```

- [ ] **Step 2: Schreibe den fehlschlagenden Konfigurationstest**

```ts
describe('database config', () => {
  it('disables synchronize', () => {
    const config = createDatabaseConfig(
      'postgresql://test:test@localhost:5432/test',
    );

    expect(config.synchronize).toBe(false);
  });
});
```

- [ ] **Step 3: Implementiere `createDatabaseConfig`**

```ts
export function createDatabaseConfig(url: string) {
  return {
    type: 'postgres' as const,
    url,
    autoLoadEntities: true,
    synchronize: false,
  };
}
```

- [ ] **Step 4: Registriere TypeORM**

```ts
TypeOrmModule.forRootAsync({
  useFactory: () => createDatabaseConfig(
    process.env.DATABASE_URL as string,
  ),
})
```

- [ ] **Step 5: Lege TypeORM CLI DataSource an**

`data-source.ts` verwendet dieselbe `DATABASE_URL`, Entities und Migrationspfade.

- [ ] **Step 6: Führe Tests aus**

```bash
npm test --workspace=@ashen-realms/api
```

Expected: PASS.

- [ ] **Step 7: Commit**

```bash
git add apps/api
git commit -m "feat: add postgres typeorm foundation"
```

---

### Task 3: Health API und globaler `/api` Prefix

**Files:**
- Create: `apps/api/src/health/health.controller.ts`
- Create: `apps/api/src/health/health.module.ts`
- Create: `apps/api/src/health/health.controller.spec.ts`
- Modify: `apps/api/src/app.module.ts`
- Modify: `apps/api/src/main.ts`

**Interfaces:**
- Produces: `GET /api/health`
- Consumes: Nest application

- [ ] **Step 1: Schreibe Controller-Test**

```ts
it('returns ok', () => {
  expect(controller.getHealth()).toEqual({ status: 'ok' });
});
```

- [ ] **Step 2: Implementiere Controller**

```ts
@Controller('health')
export class HealthController {
  @Get()
  getHealth() {
    return { status: 'ok' };
  }
}
```

- [ ] **Step 3: Setze globalen Prefix**

```ts
app.setGlobalPrefix('api');
```

- [ ] **Step 4: Teste API**

```bash
npm run start:dev --workspace=@ashen-realms/api
curl http://localhost:3000/api/health
```

Expected:

```json
{"status":"ok"}
```

- [ ] **Step 5: Commit**

```bash
git add apps/api
git commit -m "feat: add health endpoint"
```

---

### Task 4: Angular App Shell

**Files:**
- Create: `apps/web/src/app/layout/app-shell/*`
- Create: `apps/web/src/app/layout/top-bar/*`
- Create: `apps/web/src/app/layout/side-navigation/*`
- Create: `apps/web/src/app/layout/game-footer/*`
- Create: `apps/web/src/app/features/world/world-page/*`
- Modify: `apps/web/src/app/app.routes.ts`
- Modify: `apps/web/src/app/app.component.*`
- Test: component specs generated beside components

**Interfaces:**
- Produces: `/world` screen shell
- Consumes: UI Visual Design Specification

- [ ] **Step 1: Schreibe App-Shell-Test**

Testet, dass Topbar, Navigation, Router Outlet und Footer vorhanden sind.

- [ ] **Step 2: Implementiere Shell**

Struktur:

```html
<div class="app-shell">
  <app-top-bar />
  <app-side-navigation />
  <main class="app-shell__content">
    <router-outlet />
  </main>
  <app-game-footer />
</div>
```

- [ ] **Step 3: Erzeuge `/world` Route**

```ts
{
  path: 'world',
  loadComponent: () =>
    import('./features/world/world-page/world-page.component')
      .then(m => m.WorldPageComponent),
}
```

- [ ] **Step 4: Setze Default Redirect**

```ts
{ path: '', pathMatch: 'full', redirectTo: 'world' }
```

- [ ] **Step 5: Führe Tests aus**

```bash
npm test --workspace=@ashen-realms/web
```

- [ ] **Step 6: Commit**

```bash
git add apps/web
git commit -m "feat: add game application shell"
```

---

### Task 5: Angular API Proxy und typed API Client

**Files:**
- Create: `apps/web/proxy.conf.json`
- Create: `apps/web/src/app/core/api/api-client.service.ts`
- Create: `apps/web/src/app/core/api/api-client.service.spec.ts`
- Modify: `apps/web/package.json`
- Modify: Angular dev-server config if required

**Interfaces:**
- Produces: `ApiClientService.get<T>()`, `post<T>()`
- Consumes: relative `/api/*` URLs

- [ ] **Step 1: Schreibe Proxy-Datei**

Verwende Abschnitt 11.

- [ ] **Step 2: Schreibe API-Client-Test**

Der Test verifiziert, dass `/api/health` relativ aufgerufen wird.

- [ ] **Step 3: Implementiere Client**

```ts
@Injectable({ providedIn: 'root' })
export class ApiClientService {
  constructor(private readonly http: HttpClient) {}

  get<T>(path: string) {
    return this.http.get<T>(`/api${path}`);
  }

  post<T>(path: string, body: unknown) {
    return this.http.post<T>(`/api${path}`, body);
  }
}
```

- [ ] **Step 4: Passe Dev-Start an**

Angular Dev Server startet mit Proxy.

- [ ] **Step 5: Commit**

```bash
git add apps/web
git commit -m "feat: add frontend api client"
```

---

### Task 6: Erste Content-Entities

**Files:**
- Create: `apps/api/src/world/entities/location-definition.entity.ts`
- Create: `apps/api/src/world/entities/location-connection.entity.ts`
- Create: `apps/api/src/monsters/entities/monster-definition.entity.ts`
- Create: `apps/api/src/monsters/entities/location-monster.entity.ts`
- Create: `apps/api/src/items/entities/item-definition.entity.ts`
- Create: corresponding modules
- Test: metadata/entity tests

**Interfaces:**
- Produces: persistierbare World/Monster/Item Content-Definitionen
- Consumes: TypeORM foundation

- [ ] **Step 1: Implementiere `LocationDefinition`**

Pflichtfelder:

```text
id
key
name
description
regionKey
minRecommendedLevel
maxRecommendedLevel
dangerLevel
isSafe
huntingEnabled
artworkPath
createdAt
updatedAt
```

`key` erhält einen Unique Index.

- [ ] **Step 2: Implementiere `LocationConnection`**

Pflichtfelder:

```text
id
fromLocation
toLocation
travelDurationSeconds
ambushChance
enabled
```

- [ ] **Step 3: Implementiere `MonsterDefinition`**

Pflichtfelder:

```text
id
key
name
level
maxHp
attack
armor
experienceReward
silverMin
silverMax
artworkPath
```

- [ ] **Step 4: Implementiere `LocationMonster`**

Pflichtfelder:

```text
id
location
monster
weight
encounterType
enabled
```

- [ ] **Step 5: Implementiere `ItemDefinition`**

Pflichtfelder:

```text
id
key
name
description
type
equipmentSlot
rarity
tier
requiredLevel
weaponDamage
bonusHp
bonusAttack
bonusArmor
sellPrice
iconPath
```

- [ ] **Step 6: Generiere Migration**

```bash
npm run typeorm:migration:generate --workspace=@ashen-realms/api -- InitialContent
```

- [ ] **Step 7: Prüfe generiertes SQL**

Keine Drop-/Recreate-Operationen an unerwarteten Tabellen akzeptieren.

- [ ] **Step 8: Führe Migration aus**

```bash
npm run typeorm:migration:run --workspace=@ashen-realms/api
```

- [ ] **Step 9: Commit**

```bash
git add apps/api
git commit -m "feat: add core content entities"
```

---

### Task 7: Character, Inventory und Equipment State

**Files:**
- Create: `apps/api/src/users/entities/user.entity.ts`
- Create: `apps/api/src/characters/entities/character.entity.ts`
- Create: `apps/api/src/inventory/entities/character-item.entity.ts`
- Create: `apps/api/src/equipment/entities/character-equipment.entity.ts`
- Create: `apps/api/src/characters/character-stats.service.ts`
- Test: `character-stats.service.spec.ts`

**Interfaces:**
- Produces: `EffectiveCharacterStats`
- Consumes: `ItemDefinition`

- [ ] **Step 1: Schreibe failing stats test**

Beispiel:

```ts
it('combines base stats with equipped items', () => {
  const result = service.calculate(character, equipment);

  expect(result.attack).toBe(12);
  expect(result.weaponDamage).toBe(15);
  expect(result.armor).toBe(25);
  expect(result.maxHp).toBe(150);
});
```

- [ ] **Step 2: Implementiere Entities**

Nutze UUID Primary Keys und Foreign Keys.

- [ ] **Step 3: Implementiere `CharacterStatsService`**

Combat Power:

```ts
const combatPower =
  maxHp / 10 +
  attack * 2 +
  weaponDamage * 2 +
  armor * 1.5;
```

- [ ] **Step 4: Führe Unit Tests aus**

```bash
npm test --workspace=@ashen-realms/api -- character-stats
```

- [ ] **Step 5: Generiere und prüfe Migration**

- [ ] **Step 6: Commit**

```bash
git add apps/api
git commit -m "feat: add character inventory equipment state"
```

---

### Task 8: Travel Vertical Slice

**Files:**
- Create: `apps/api/src/travel/entities/travel.entity.ts`
- Create: `apps/api/src/travel/travel.service.ts`
- Create: `apps/api/src/travel/travel.controller.ts`
- Create: service/controller tests

**Interfaces:**
- Produces:
  - `POST /api/travel`
  - `GET /api/travel/current`
- Consumes: Character current location, LocationConnection

- [ ] **Step 1: Schreibe Test für ungültiges Reiseziel**

Nicht verbundene Locations müssen mit Domain-Fehler abgelehnt werden.

- [ ] **Step 2: Schreibe Test für gültige Reise**

Testet `startedAt`, `arrivesAt`, Origin und Target.

- [ ] **Step 3: Implementiere `startTravel`**

Nur der Server berechnet `arrivesAt`.

- [ ] **Step 4: Implementiere `completeTravelIfDue`**

Vor Ablauf darf `currentLocationId` nicht wechseln.

- [ ] **Step 5: Implementiere Controller**

Request enthält nur:

```json
{
  "targetLocationId": "uuid"
}
```

- [ ] **Step 6: Führe Tests aus**

- [ ] **Step 7: Commit**

```bash
git add apps/api
git commit -m "feat: add server authoritative travel"
```

---

### Task 9: Hunt und Encounter Slice

**Files:**
- Create: `apps/api/src/hunting/entities/hunt.entity.ts`
- Create: `apps/api/src/hunting/entities/hunt-encounter.entity.ts`
- Create: `apps/api/src/hunting/hunting.service.ts`
- Create: `apps/api/src/hunting/hunting.controller.ts`
- Create: tests

**Interfaces:**
- Produces:
  - `POST /api/hunts`
  - persisted HuntEncounter IDs
- Consumes: LocationMonster weighted pool

- [ ] **Step 1: Schreibe Test für Jagd in nicht jagdbarem Ort**

Expected: Domain-Fehler.

- [ ] **Step 2: Schreibe deterministischen Weight-Test**

Random-Quelle im Test injizieren, damit Auswahl reproduzierbar ist.

- [ ] **Step 3: Implementiere `startHunt`**

Server erzeugt Encounter-Datensätze.

- [ ] **Step 4: Verhindere frei wählbare Monster IDs**

Combat darf später nur aus gültiger Encounter-ID starten.

- [ ] **Step 5: Führe Tests aus**

- [ ] **Step 6: Commit**

```bash
git add apps/api
git commit -m "feat: add hunting encounters"
```

---

### Task 10: Combat Engine

**Files:**
- Create: `apps/api/src/combat/combat-engine.service.ts`
- Create: `apps/api/src/combat/combat-engine.types.ts`
- Create: `apps/api/src/combat/combat-engine.service.spec.ts`

**Interfaces:**
- Produces:
  - `resolveAction(state, action): CombatEngineResult`
- Consumes:
  - fixed player stats
  - monster stats
  - action input

- [ ] **Step 1: Schreibe Schadensformel-Test**

```ts
expect(
  calculateDamage({
    attack: 12,
    weaponDamage: 15,
    armor: 20,
    multiplier: 1,
  }),
).toBe(20);
```

- [ ] **Step 2: Implementiere Schadensformel**

```ts
const raw = (attack + weaponDamage) * multiplier;
const mitigated = raw * 60 / (60 + armor);
return Math.max(1, Math.round(mitigated));
```

- [ ] **Step 3: Schreibe Tests für Aktionen**

Abdecken:

```text
ATTACK = 100 %
HEAVY_STRIKE = 160 %
SHIELD_BASH = 70 % + interrupt
DEFEND = 50 % incoming reduction
POTION = 35 % max HP
```

- [ ] **Step 4: Implementiere minimale Actions**

Keine Persistenz in diesem Service.

- [ ] **Step 5: Führe Tests aus**

- [ ] **Step 6: Commit**

```bash
git add apps/api/src/combat
git commit -m "feat: add deterministic combat engine"
```

---

### Task 11: Persistierter Combat Flow

**Files:**
- Create: `apps/api/src/combat/entities/combat.entity.ts`
- Create: `apps/api/src/combat/entities/combat-event.entity.ts`
- Create: `apps/api/src/combat/combat.service.ts`
- Create: `apps/api/src/combat/combat.controller.ts`
- Create: integration tests

**Interfaces:**
- Produces:
  - `POST /api/hunt-encounters/:id/attack`
  - `POST /api/combats/:id/actions`
- Consumes: CombatEngineService, HuntEncounter

- [ ] **Step 1: Teste Combat-Erstellung nur aus gültigem Encounter**

- [ ] **Step 2: Persistiere Startzustand**

Speichere:

```text
round
playerCurrentHp
monsterCurrentHp
playerState jsonb
monsterState jsonb
```

- [ ] **Step 3: Implementiere Action Endpoint**

Request:

```json
{
  "action": "ATTACK"
}
```

Kein Damage-Feld zulassen.

- [ ] **Step 4: Persistiere CombatEvents**

Jede Runde erhält geordnete Events.

- [ ] **Step 5: Commit**

```bash
git add apps/api/src/combat
git commit -m "feat: persist combat rounds and events"
```

---

### Task 12: Loot, Inventory und Equipment End-to-End

**Files:**
- Create: `apps/api/src/loot/entities/loot-table.entity.ts`
- Create: `apps/api/src/loot/entities/loot-table-entry.entity.ts`
- Create: `apps/api/src/loot/loot.service.ts`
- Create: `apps/api/src/inventory/inventory.controller.ts`
- Create: `apps/api/src/equipment/equipment.controller.ts`
- Create: tests

**Interfaces:**
- Produces:
  - loot grants
  - `GET /api/inventory`
  - `GET /api/equipment`
  - `POST /api/equipment`
- Consumes: Combat WON state

- [ ] **Step 1: Schreibe deterministischen Loot-Roll-Test**

Random-Quelle injizieren.

- [ ] **Step 2: Implementiere Loot-Tabellen**

- [ ] **Step 3: Vergib Loot in einer DB-Transaktion**

Atomar:

```text
Combat WON
XP
Silver
CharacterItem
Combat status
```

- [ ] **Step 4: Implementiere Inventory GET**

- [ ] **Step 5: Implementiere Equip Endpoint**

Validierungen:

```text
Item gehört Character
Item ist ausrüstbar
Slot stimmt
requiredLevel erfüllt
```

- [ ] **Step 6: Führe End-to-End-Test aus**

Flow:

```text
Hunt
→ Encounter
→ Combat
→ Win
→ Loot
→ Inventory
→ Equip
→ Stats increased
```

- [ ] **Step 7: Commit**

```bash
git add apps/api
git commit -m "feat: complete loot inventory equipment loop"
```

---

### Task 13: Seed des Mini-Slices

**Files:**
- Create: `apps/api/src/database/seeds/vertical-slice.seed.ts`
- Create: seed command
- Test: seed integration test

**Interfaces:**
- Produces:
  - Südtor
  - Verbrannte Straße
  - Aschenratte
  - Straßenräuber
  - drei Items
  - Loot tables
- Consumes: content entities

- [ ] **Step 1: Seed Locations**

```text
south-gate
burned-road
```

- [ ] **Step 2: Seed Verbindung**

```text
south-gate -> burned-road
duration = 10
ambushChance = 0.05
```

- [ ] **Step 3: Seed Monster**

```text
ash-rat
road-bandit
```

- [ ] **Step 4: Seed Items**

```text
worn-short-sword
bandit-blade
bandit-hood
```

- [ ] **Step 5: Seed Loot**

- [ ] **Step 6: Seed idempotent machen**

Wiederholtes Ausführen darf keine Duplikate erzeugen.

- [ ] **Step 7: Commit**

```bash
git add apps/api/src/database
git commit -m "feat: seed first playable content slice"
```

---

### Task 14: Angular World → Hunt → Combat Flow

**Files:**
- Create/Modify feature components under:
  - `apps/web/src/app/features/world/`
  - `apps/web/src/app/features/hunting/`
  - `apps/web/src/app/features/combat/`
  - `apps/web/src/app/features/inventory/`
- Create API state services
- Test components/services

**Interfaces:**
- Produces: first playable browser flow
- Consumes: REST endpoints from Tasks 8–12

- [ ] **Step 1: World Screen lädt aktuellen Ort**

- [ ] **Step 2: Reisebutton startet Travel**

- [ ] **Step 3: Countdown nutzt ausschließlich `arrivesAt` vom Server**

- [ ] **Step 4: Jagdseite rendert HuntEncounter Cards**

- [ ] **Step 5: Angreifen öffnet Combat Route**

- [ ] **Step 6: Combat Buttons senden nur Action Enum**

- [ ] **Step 7: CombatEvents treiben Animation und Log**

- [ ] **Step 8: Nach Sieg öffnet Loot-Zusammenfassung**

- [ ] **Step 9: Inventar erlaubt Ausrüsten**

- [ ] **Step 10: Verifiziere gesamten Browserflow**

- [ ] **Step 11: Commit**

```bash
git add apps/web
git commit -m "feat: connect first playable frontend loop"
```

---

### Task 15: NestJS Static Hosting für Angular

**Files:**
- Modify: `apps/api/src/app.module.ts`
- Modify: `apps/api/src/main.ts`
- Modify: build scripts
- Test: production static-serving smoke test

**Interfaces:**
- Produces: ein NestJS-Prozess für API + Angular
- Consumes: Angular production build

- [ ] **Step 1: Installiere Static Serving**

```bash
npm install --workspace=@ashen-realms/api @nestjs/serve-static
```

- [ ] **Step 2: Konfiguriere Angular-Ausgabepfad**

Build-Ausgabe in gemeinsamen Runtime-Pfad kopieren.

- [ ] **Step 3: Konfiguriere SPA-Fallback**

`/api/*` bleibt API; andere unbekannte Pfade liefern `index.html`.

- [ ] **Step 4: Baue Production**

```bash
npm run build
```

- [ ] **Step 5: Starte nur NestJS**

```bash
node apps/api/dist/main.js
```

- [ ] **Step 6: Prüfe**

```bash
curl http://localhost:3000/api/health
curl -I http://localhost:3000/world
```

Expected:
- API liefert JSON
- `/world` liefert Angular HTML

- [ ] **Step 7: Commit**

```bash
git add apps package.json
git commit -m "feat: serve angular through nest"
```

---

### Task 16: Docker Production Image

**Files:**
- Create: `Dockerfile`
- Create: `.dockerignore`
- Modify: `README.md`
- Test: container smoke test

**Interfaces:**
- Produces: ein deploybares Ashen-Realms-Image
- Consumes: root build

- [ ] **Step 1: Erstelle `.dockerignore`**

Mindestens:

```text
node_modules
.git
dist
coverage
.env
```

- [ ] **Step 2: Implementiere Multi-Stage Dockerfile**

Basis ist Node 24 Alpine.

- [ ] **Step 3: Baue Image**

```bash
docker build -t ashen-realms:local .
```

- [ ] **Step 4: Starte Container**

```bash
docker run --rm \
  -p 3000:3000 \
  -e DATABASE_URL="$DATABASE_URL" \
  -e JWT_ACCESS_SECRET="local-access" \
  -e JWT_REFRESH_SECRET="local-refresh" \
  ashen-realms:local
```

- [ ] **Step 5: Smoke Test**

```bash
curl http://localhost:3000/api/health
curl -I http://localhost:3000/world
```

- [ ] **Step 6: Verifiziere genau einen Runtime-Prozess**

Container enthält keinen Angular Dev Server und keinen PostgreSQL-Prozess.

- [ ] **Step 7: Commit**

```bash
git add Dockerfile .dockerignore README.md
git commit -m "chore: add single-container production image"
```

---

# 22. Definition of Done für das technische Fundament

Das Fundament ist fertig, wenn:

- `npm install` am Root funktioniert.
- Angular und NestJS aus einem npm-Workspace gebaut werden.
- PostgreSQL über TypeORM erreichbar ist.
- Migrationen funktionieren.
- `synchronize` deaktiviert ist.
- `/api/health` erreichbar ist.
- Angular lokal über Proxy gegen NestJS arbeitet.
- Production Build Angular über NestJS ausliefert.
- ein Docker-Image beide Anwendungsteile enthält.
- der Runtime-Container nur NestJS startet.
- PostgreSQL außerhalb des App-Containers bleibt.
- der Mini-Slice Reise → Jagd → Kampf → Loot → Equipment funktioniert.
- Combat und Loot serverseitig bestimmt werden.
- Unit- und Integrationstests grün sind.

---

# 23. Nach diesem Plan

Erst danach den Content schrittweise erweitern:

```text
Aschenfelder vollständig
→ Dämmerwald
→ Vergessene Ruinen
```

Anschließend:

```text
Quests
→ Gebietswährungen
→ Händler
→ Sets
→ Auth-Härtung
→ Social / Realtime
→ CMS
```

Die Architektur wird nicht vorab für diese späteren Systeme aufgebläht.

---

# 24. Architekturentscheidungen V1

| Bereich | Entscheidung |
|---|---|
| Frontend | Angular 22 |
| Backend | NestJS 11 |
| Sprache | TypeScript |
| ORM | TypeORM 1.x |
| Datenbank | PostgreSQL |
| Monorepo | npm Workspaces |
| Monorepo Framework | keines |
| API | REST |
| API Prefix | `/api` |
| Realtime | noch keines |
| Runtime | Node.js 24 LTS |
| Deployment | 1 App-Docker-Container |
| PostgreSQL | separater Dienst |
| Assets V1 | im Angular Bundle |
| Architektur | modularer Monolith |
| Spiellogik | serverautoritativ |
| DB Schema | TypeORM Migrationen |
| `synchronize` | immer false außerhalb Wegwerf-Tests |
