First pass at full refactor to 2.0 design

Restructures the game around the cyclical run loop from Game Design Doc V2:
5 waves = a cycle, 3 cycles = a phase, each phase ends in a boss.

- New TD.Gameplay.Waves: WaveGroup / PhaseDefinition / RunDefinition author the
  run as draggable weighted pools; RunState owns phase/cycle position, the drawn
  wave slots, and the per-slot enemy buff sets. WaveManager's flat wave array is
  gone -- it now only runs the encounter RunState points at.
- New TD.Gameplay.EnemyUpgrades: the post-wave enemy-buff vote, with public
  live-replicated ballots so the HUD can show who voted for what.
- Inter-wave flow is now strictly sequential: draft -> vote -> build, each stage
  ending early once every player has acted.
- Enemy abilities inverted from per-instance random rolls to deterministic,
  stacking per-wave-slot sets. Six cards ship: Split (reworked), Flight, Blink,
  No Bounty, Gold Theft, Double Up.
- Tower upgrades are a two-step tree: a draft pick unlocks a node, gold converts
  an already-placed tower in place.
- Boss encounters flag their enemies and drive a boss HP bar.
- Player cap reduced to 3 via MatchRules.MaxPlayers.
- GoldConfig is now keyed by global encounter number rather than wave index.

Compiles clean; NOT yet verified in-engine. Editor wiring still required --
see Docs/2.0_Setup_Checklist.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Matt F 2026-07-30 17:45:11 -07:00
parent 7e5c3a8279
commit 4892d7253d
64 changed files with 4023 additions and 344 deletions

View file

@ -10,10 +10,11 @@ Last substantial update: 2026-07-14.
## Game overview
A **co-op tower-defense / roguelike hybrid** for up to 9 players.
A **co-op tower-defense / roguelike hybrid** for up to **3 players** (reduced from 9 in the 2.0 design as a scope cut — see `TD.Core.MatchRules.MaxPlayers`; the `PlayerSlot` enum still runs to 9 so baked `LevelData` owner grids stay valid).
- **Maze defense (Wintermaul-style):** players build towers to force enemies along a longer path through their own zone. Lives are a **shared pool**; gold is **per-player**.
- **Roguelike layer (now core to the design):** every player starts with the **same three towers** and builds out a personal "deck"/"build" through a **3-option draft** presented at match start and after every wave. Choices span new towers, systemic upgrades, builder abilities, enemy debuffs, and relic quests. See [`Project_Roadmap.md`](Project_Roadmap.md) for the full design.
- **Cyclical run structure (2.0):** 5 waves = a **cycle**, 3 cycles = a **phase**, each phase ends in a **boss**, 3 phases wins the run. A phase draws 5 waves with **distinct enemy types** and replays them every cycle. Beating the last phase's boss is Victory.
- **Roguelike layer (core):** every player starts with the same three towers and grows a personal deck through a **3-option draft**. After each wave the sequence is: personal draft → shared **enemy-buff vote** (open ballots, visible per-player) → build countdown. The vote permanently buffs the wave just cleared, so it comes back harder next cycle; buffs are wiped when a new phase draws fresh waves. See [`Project_Roadmap.md`](Project_Roadmap.md) for the full design.
- **Target platform:** Steam (Windows / Linux / Steam Deck).
- **Visual direction (aspirational):** "painted tabletop miniature" look with Spider-Verse-style stepped (on-2s) enemy animation. Current visuals are **placeholder** (primitive meshes / cones, sourced creature models).
@ -32,11 +33,13 @@ Unity **6.4 (6000.4.4f1)**, URP, IL2CPP, .NET Standard 2.1, Linear color space,
## Architecture & conventions
- **Server-authoritative gameplay; local-only UI/visual state.** Only gameplay-meaningful state is networked. Selection, placement ghosts, animation, and the paint cursor are client-local.
- **Data-driven via ScriptableObjects:** `TowerDefinition`, `EnemyDefinition`, `WaveDefinition`, `RaceDefinition`, `GoldConfig`, `BuffDefinition`/`BuffCategory`, `DraftOption` (+ `NewTowerDraftOption`). Designers tune stats in assets, not code.
- **Per-player state pattern:** `NetworkBehaviour`s on the **Player prefab** with a static `GetForClient(clientId)` / `Local` registry. Current set: `PlayerGoldManager`, `PlayerMatchState`, `PlayerBuffManager`, `PlayerTowerDeck`, `PlayerDraft`.
- **Networked identifiers are catalog indices.** `TowerTypeId` indexes `TowerPlacementManager.towerDefinitions[]`; `DraftOptionId` indexes `DraftPool`. Stable **within a match**, not across sessions — cross-match persistence will need stable IDs (asset GUID or a serialized StableId).
- **Namespaces:** `TD.Core` (enums, palettes, grid math), `TD.Gameplay` (builder, placement, match/wave/pathfinding state, economy, enemies, deck), `TD.Gameplay.Draft` (draft system), `TD.Combat` (TowerCombat/Projectile), `TD.Levels` (in-engine authoring + bake), `TD.UI`, `TD.Net`.
- **Scenes:** `MainMenu``Lobby` → a Match level (`9Player`, `Main`).
- **Data-driven via ScriptableObjects:** `TowerDefinition`, `EnemyDefinition`, `WaveDefinition`, `RaceDefinition`, `GoldConfig`, `DraftOption` (+ subclasses), `EnemyAbilityDefinition` (+ subclasses), `EnemyUpgradeOption` (+ subclasses), and the run structure — `WaveGroup``PhaseDefinition``RunDefinition`, plus `EnemyUpgradeGroup`. Designers tune stats in assets, not code.
- **Pools are authored as draggable groups.** Wave and enemy-buff pools are lists of *group* assets, so rebalancing a phase means dragging a group between phases rather than re-authoring entries. Draw weights are **relative** 01 values on each entry (`[PoolWeight]`), normalized at draw time; a zero weight means "never drawn" and the inspector warns inline.
- **Per-player state pattern:** `NetworkBehaviour`s on the **Player prefab** with a static `GetForClient(clientId)` / `Local` registry. Current set: `PlayerGoldManager`, `PlayerMatchState`, `PlayerTowerDeck`, `PlayerTowerUpgrades`, `PlayerDraft`, `PlayerSpellLoadout`, `BuilderUpgradeManager`.
- **Networked identifiers are catalog indices.** `TowerTypeId` indexes `TowerPlacementManager.towerDefinitions[]`; `DraftOptionId` indexes `DraftPool`; `EnemyUpgradeOptionId` indexes `EnemyUpgradePool`; wave ids index a flat list `RunDefinition` builds by walking its phases/groups/entries in declaration order (identical on every peer). Stable **within a match**, not across sessions.
- **Run progression lives in `RunState`, not `WaveManager`.** `RunState` owns phase/cycle position, the drawn wave slots, and the per-slot enemy-buff sets; `WaveManager` only runs the encounter it is pointed at and calls `ServerAdvance()` when the field clears.
- **Namespaces:** `TD.Core` (enums, palettes, grid math, match rules), `TD.Gameplay` (builder, placement, match/pathfinding state, economy, enemies, deck), `TD.Gameplay.Waves` (run structure + progression), `TD.Gameplay.Draft` (per-player draft), `TD.Gameplay.EnemyAbilities` (what buffed enemies *do*), `TD.Gameplay.EnemyUpgrades` (the vote + its cards), `TD.Combat` (TowerCombat/Projectile), `TD.Levels` (in-engine authoring + bake), `TD.UI`, `TD.Net`.
- **Scenes:** `MainMenu``Lobby` → a Match level (`9Player`, `Main`). `9Player` is **legacy** at 3-player scope — a new 3-player map is pending.
### Engineering principles (carried across sessions)