TRXTD/README.md
Tronax aa68343ac2
feat(balance): 30-wave campaign, 5 new enemy types, steeper per-wave scaling
The campaign was too easy: a handful of un-upgraded towers stopped
everything. This makes the game substantially harder across three axes.

Campaign extended from 20 to 30 hand-tuned waves (endless now starts at
31). Victory messages, HUD label (via store.totalWaves synced from
TOTAL_WAVES), start screen, lobby and README updated.

Five new enemy types debut in later waves, each with a hand-drawn
canvas look:
- Brute (wave 8): fast tank hybrid, 130 HP, 2 lives
- Phantom (wave 12): fast flyer with real HP, 2 lives
- Scorpion (wave 14): very fast ground, 2 lives
- Golem (wave 18): walking bunker, 560 HP, 3 lives
- Dragon (wave 22): flying boss, 950 HP, 3 lives, gets its own top
  boss health bar like the boss

Per-wave HP scaling steepened (1 + 0.16m + 0.025m², was 0.18m + 0.02m²):
wave 10 now ~4.6x (was 4.2x), wave 20 ~13.2x (was 11.6x), wave 30 ~29x.
Late waves mix the new types into heavy compositions; wave 30 is the
final wall (3 bosses, 2 dragons, 4 golems, 12 scorpions). Endless waves
scale all ten types with bosses every 5 and dragons from endless+2.

Balance validated with the headless greedy-bot simulation: the bot
(capped around tower level 5 on fixed spots) previously trivially won
the campaign and now dies at wave 29 — clearing wave 30 requires
evolved towers (level 6+), research bonuses and good placement. The
sim docs were updated to describe this new tuning philosophy.

New render smoke test (npm test): draws all ten enemy kinds through
the real renderer with a stub 2D context, two frames each with slow/
DoT/flash effects active, guarding every drawEnemy code path. 55 checks
green, build clean. Browser-verified: campaign starts and HUD shows
"Welle x/30".
2026-08-17 20:29:47 +02:00

9.9 KiB
Raw Blame History

TRXTD Tower Defense im Browser

Ein vollständiges Tower-Defense-Spiel, gebaut mit Vue 3 + TypeScript + Vite. Die Spiellogik läuft in reinem TypeScript auf einem HTML-Canvas (60 FPS), Vue kümmert sich um HUD, Menüs und Panels.

Enthält Solo-Kampagne, 2-Spieler-Coop und 1v1-Duell (Online/Lokal per WebSocket).

Schnellstart

  1. Abhängigkeiten installieren:

    npm install
    
  2. Frontend (Vite) und Multiplayer-Server starten:

    # Terminal 1: Dev-Server
    npm run dev        # → http://localhost:5173
    
    # Terminal 2: Multiplayer-Relay (nur für Coop / 1v1 nötig)
    npm run server     # → ws://localhost:3001
    
  3. Browser öffnen unter http://localhost:5173.


🐳 Docker

Das Spiel gibt es als All-in-One-Container: Frontend (statisch) und Multiplayer-Server teilen sich einen Port ideal für Self-Hosting.

# Image bauen (Multi-Stage-Build: Build → schlankes Runtime-Image)
docker build -t trxtd:latest .

# Container starten
docker run -d --name trxtd -p 3001:3001 trxtd:latest

# Oder mit Docker Compose (empfohlen)
docker compose up -d

Danach läuft das komplette Spiel inkl. Online-Multiplayer unter http://localhost:3001.

Enthaltene Härtung:

  • Multi-Stage-Build (kein Build-Tooling im Runtime-Image)
  • Läuft als unprivilegierter Nutzer (node)
  • HEALTHCHECK über /health
  • /assets/ mit immutablen Cache-Headern, SPA-Fallback für alle anderen Routen
  • WebSocket-Sicherheit: Payload-Limit (4 KB), Rate-Limiting (30 Aktionen/s), Schema-Validierung, Ping/Pong-Heartbeat

Nützliche Umgebungsvariablen:

Variable Default Bedeutung
PORT 3001 HTTP- und WebSocket-Port
DIST_DIR ./dist Ordner mit den statischen Dateien
ALLOWED_ORIGINS Zusätzlich erlaubte WebSocket-Origins (Komma-Liste, z. B. https://spiel.example.com)

Öffentliches Hosting hinter Reverse Proxy (Nginx Proxy Manager / NPMplus):

  1. Container nur im internen Netz erreichbar machen (Port 3001 nicht am Router freigeben!), z. B.:
    # docker-compose.yml — gemeinsames Netzwerk mit dem Proxy
    services:
      trxtd:
        build: .
        restart: unless-stopped
        expose: ["3001"]          # intern, kein ports:-Mapping nach außen
      # networks: proxy_net ...
    
  2. In NPM/NPMplus einen Proxy Host anlegen:
    • Domain: spiel.example.com (DNS-A-Record auf den Server)
    • Scheme: http · Forward Hostname: Containername (gemeinsames Docker-Netz) oder Server-LAN-IP · Port: 3001
    • Websockets Support: ✔ aktivieren (zwingend erforderlich für Multiplayer!)
    • Block Common Exploits: ✔ · Cache Assets: ✘ (der Server setzt eigene Cache-Header)
    • SSL-Tab: Let's-Encrypt-Zertifikat anfordern, Force SSL + HTTP/2 aktivieren
  3. Fertig das Spiel (inkl. Coop/1v1 über wss://) läuft unter https://spiel.example.com.

Serverseitige Härtung (aktiv): CSP-/Frame-/Referrer-Header · Origin-Check gegen Cross-Site-WebSocket-Hijacking · X-Forwarded-For-Auswertung nur aus privaten Proxy-Netzen · max. 500 WebSocket-Verbindungen total / 20 pro IP · max. 300 Räume · Payload-Limit 4 KB · Rate-Limit 30 Aktionen/s · Schema-Validierung · Ping/Pong-Heartbeat · Raum-Timeout 30 min.

Manuell ohne Docker im Produktionsmodus starten:

npm run build
npm run server   # serves dist/ + ws auf :3001

🛡️ Admin-Dashboard (/admin)

Live-Übersicht über den Server: wer gerade Solo spielt (Karte, Schwierigkeit, Welle — angemeldete Spieler senden dazu alle 20 s einen Heartbeat), offene Multiplayer-Räume, Account-Statistiken und die Nutzerliste. Aktualisiert sich alle 5 s automatisch.

Freischalten über die Umgebungsvariable ADMIN_USERS (Komma-getrennte Benutzernamen):

environment:
  - ADMIN_USERS=deinaccount

Dann als eines dieser Konten unter https://deine-domain/admin anmelden. Solo-Spiele laufen zwar lokal im Browser, aber angemeldete Spieler melden ihren Spielstatus an den Server; Gäste bleiben unsichtbar.


Spielmodi

1. Solo-Kampagne

Verteidige deine Basis gegen 20 handgetunte Wellen (Leicht: 30 Leben, Normal: 20 Leben, Schwer: 12 Leben). Nach Welle 20 geht es auf Wunsch im Endlos-Modus weiter.

2. 🤝 2-Spieler-Coop

  • Gemeinsame Basis & gemeinsames Gold Sprecht euch ab!
  • Eigene Türme: Deine Türme haben einen blauen Ring, die deines Partners einen orangenen Ring (jeder kann nur seine eigenen Türme upgraden oder verkaufen).
  • Gegner haben 70 % mehr Leben, um der doppelten Feuerkraft standzuhalten.
  • Wellen starten automatisch nach Countdown oder per Klick auf „Nächste Welle" (beide können klicken).

3. ⚔ 1v1-Duell (Tower-Battles-Prinzip)

  • Jeder Spieler verteidigt sein eigenes Spielfeld.
  • Über ein Live-Miniaturfenster (PiP) siehst du in Echtzeit das Spielfeld deines Gegners mit allen Türmen und Gegnern.
  • Rush-Mechanik: Schicke gegen Gold (60 🪙 + 8 🪙/Welle) eine Welle schneller Läufer auf die Basis des Gegners!
  • Siegbedingungen:
    • Basis des Gegners fällt auf 0 Leben → Sofortsieg!
    • Nach 20 Wellen gewinnt, wer mehr Leben übrig hat (bei Gleichstand entscheiden die Punkte).

Multiplayer-Raum-System

  • Klicke auf dem Startbildschirm auf „Coop-Raum erstellen" oder „1v1-Raum erstellen".
  • Gib deinem Mitspieler den 4-stelligen Raum-Code (z. B. 5SK7).
  • Der Mitspieler tippt seinen Namen + den Code ein und klickt auf „Beitreten".
  • Der Host startet das Spiel, sobald beide in der Lobby sind.
  • Funktioniert im lokalen Netzwerk (LAN / WLAN) für Freunde über das Internet einfach Port 3001 freigeben oder über ngrok/Tailscale leiten.

🌟 9-Level-Evolutionssystem

Jeder Turm hat 9 Ausbaustufen, aufgeteilt in 3 Evolutions-Stufen:

Tier Level Farbe Bedeutung
Basis 13 🟡 Gelb Höherer Grundschaden, Reichweite & Feuerrate
Evolution I 46 🟠 Orange Neue Spezialfähigkeit schaltet sich frei
Evolution II 79 🔵 Blau Finale Evolutionsstufe mit verheerenden Effekten

Die Evolutionen im Detail

Turm 🟡 Basis (L13) 🟠 Evolution I (L46) 🔵 Evolution II (L79)
🏹 Bogenturm Schneller Einzelschuss Mehrfachschuss: Feuert 23 Pfeile gleichzeitig auf verschiedene Ziele Giftpfeile + 4-Fach: Bis zu 4 Pfeile + starker Gift-DoT (28 Schaden/s)
🧨 Kanone Flächenschaden Brandgeschosse: Hinterlässt brennenden DoT an allen getroffenen Gegnern Flak-Geschütz: Trifft ab L7 auch Flugeinheiten mit voller Explosionskraft!
❄️ Eisturm Verlangsamender Puls Permafrost: Jeder 3. Puls friert alle Gegner komplett ein (Bewegung = 0) Zerbrechlichkeit: Eingefrorene Ziele nehmen +50 % Schaden aus ALLEN Quellen!
Teslaturm Kettenblitz Betäubungs-Schock: Kettenblitze betäuben getroffene Ziele kurzzeitig Gewittersturm: Jeder 4. Schuss schlägt bei ALLEN Gegnern in Reichweite gleichzeitig ein!
💫 Laserturm Soforttreffer Prisma-Brechung: Der Laserstrahl bricht und springt auf 23 Nebenziele über Durchschlag: Der Strahl durchdringt ALLES in einer Linie bis zum Rand der Reichweite

Türme ab Level 4 erhalten im Spiel eine pulsierende Evolutions-Aura (orange ab L4, blau ab L7) und die Sterne im Turm-Panel zeigen die jeweilige Tier-Farbe.


Gegner

Kampagne: 30 Wellen (danach Endlos). Die Gegnerstärke skaliert pro Welle quadratisch — späte Wellen brauchen evolvierte Türme (Level 6+) und Forschung.

  • 🟢 Kriecher: Standard-Einheit, gut für frühes Gold
  • 🟡 Läufer: Sehr schnell Bogentürme oder Eisturm nötig
  • Panzer: Zäh, kostet 2 Leben bei Durchbruch
  • 🟣 Flieger: Ignorieren den Weg und fliegen auf halber Höhe geradeaus (nur Bogenturm, Eisturm, Tesla, Laser!)
  • 🔴 Boss: Riesige Lebensleiste, kostet 5 Leben (Wellen 10, 15, 20, 25, 30)
  • 🟤 Brute (ab Welle 8): Schneller Panzer — zäh UND flott, kostet 2 Leben
  • Phantom (ab Welle 12): Schneller Flieger mit viel Leben — Luftverteidigung Pflicht
  • 🦂 Skorpion (ab Welle 14): Extrem schnell am Boden, kostet 2 Leben
  • 🗿 Golem (ab Welle 18): Laufender Bunker, riesige Lebensleiste, kostet 3 Leben
  • 🐲 Drache (ab Welle 22): Fliegender Boss, kostet 3 Leben — eigene Boss-Leiste am oberen Rand

Hindernisse

Bäume (60 🪙) und Felsen (40 🪙) blockieren strategisch wichtige Bauplätze.

  • Klick auf das Hindernis → „⛏ Entfernen" öffnet sich oben rechts.
  • Nach dem Entfernen wird das Feld sofort bebaubar.

Steuerung

  • 15 Turm zum Bauen auswählen, Klick auf freies Feld platziert ihn
  • Klick auf Turm Details, Upgrade, Verkauf, Zielmodus
  • Klick auf Hindernis Entfernen gegen Gold
  • Leertaste nächste Welle früh starten (Frühstart-Bonus!) / Pause (nur Solo)
  • U Upgrade · V Verkaufen · T Zielmodus wechseln
  • P Pause (nur Solo) · M Ton an/aus · Esc Abbrechen
  • Rechte Maustaste Bauauswahl aufheben

Technische Details

  • Client-Side Simulation: Determinister Lockstep-Tick-Loop (30 Hz) beide Clients berechnen den exakten Spielverlauf parallel; der Server (server/server.mjs) vermittelt nur die Räume und leitet Aktionen weiter (minimaler Traffic, kein Lag bei der Bewegung).
  • Renderer: Offscreen-gepufferter HTML-Canvas mit dynamischer Vollbild-Skalierung, Retina-DPR-Support, Partikelsystem und Mündungsfeuer.
  • Sound: Web Audio API mit synthetisierten Klängen (keine externen Audio-Dateien).
  • Automatisierte Tests:
    • npm run sim Headless-Bot spielt die Solo-Kampagne zur Balancing-Prüfung durch
    • npx tsx scripts/test-mp.mts Lockstep-Determinismus-Test (3600 Ticks, byte-identischer Zustand auf beiden Seiten)
    • npx tsx scripts/test-obstacle.mts Feature-Test: Hindernis-Entfernung