UnityTowerDefense/Docs/2.0_Setup_Checklist.md
Matt F 16706a1ecf Scale difficulty by run position; wipe towers at phase end
Follow-up pass on the 2.0 refactor, closing the gap between "waves are drawn
at random" and "difficulty still lives in the enemy assets".

- Enemy health is no longer authored per enemy type. New EnemyScalingConfig
  scales a per-zone HP *budget* by encounter number; per-enemy health is that
  budget divided by the wave's enemy count. Scaling the total rather than the
  per-enemy value keeps enemy count a texture knob (few tanks vs many swarmers)
  instead of a second, uncontrolled difficulty axis. EnemyDefinition.MaxHp
  becomes HpMultiplier, a deviation around 1.0. Speed stays archetype-only and
  unscaled -- escalating it compounds with health and invalidates tower balance
  mid-run.
- GoldConfig entries no longer reference a WaveDefinition. Payout is a property
  of run position, not of which wave got drawn: WaveGoldEntry ->
  EncounterGoldEntry, Waves -> Encounters, keyed by global encounter number.
  Its inspector labels elements "Encounter N" and projects cumulative earnings.
- The wave's voted buffs now show as icon badges in the top bar, read from the
  same RunState slot the spawn path uses so the display can't drift from what
  the enemies actually carry. Hover names the buff; unillustrated cards fall
  back to a lettered badge rather than vanishing.
- Clearing a phase's boss destroys every built tower, unrefunded. Queued build
  jobs still refund -- those towers were never delivered. Runs with the field
  empty, so the walkability churn doesn't hit the re-path scheduler.
- Fixed an RPC codegen break: [ClientRpc] requires a ClientRpc suffix, unlike
  the newer [Rpc(SendTo...)] style this file doesn't use.
- Setup checklist reordered into dependency order; it previously asked for a
  RunDefinition two sections before creating one.

Also carries the editor-side asset reorganisation into Definitions/RunDefinitions
and the sprite move into Enemy/Player draft icon folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 00:32:33 -07:00

226 lines
10 KiB
Markdown
Raw Permalink 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.

# 2.0 Refactor — Editor Setup Checklist
The 2.0 code landed on `2.0_design_refactor` and compiles clean, but the new systems are
**data- and scene-driven**. Nothing runs until the assets below exist and the scene objects are
wired. Work top to bottom — each section depends on the ones above it.
Sections are in **dependency order** — every asset a step references has been created by an
earlier step. Scene wiring comes last, once there is something to assign.
---
## 0. Assets vs. scene objects
The single most useful distinction when hunting for a field:
- **`ScriptableObject` → a file in the Project window.** `WaveDefinition`, `WaveGroup`,
`PhaseDefinition`, `RunDefinition`, every ability, every card, every upgrade group.
- **`MonoBehaviour` → a component on a scene GameObject.** Only four:
`RunState`, `WaveVote`, `EnemyUpgradePool`, `EnemyAbilityPool`.
Scene components hold *references* to assets, never the other way round — `RunState` in the scene
points at the `RunDefinition` asset on disk.
Where the assets live, under `Assets/_Project/Definitions/`:
| Asset type | Folder |
|---|---|
| Abilities | `RunDefinitions/EnemyAbilities/` |
| Enemy-buff cards + upgrade groups | `RunDefinitions/Draft/EnemyDraft/` |
| Player draft options | `RunDefinitions/Draft/PlayerDraft/` |
| Wave groups, phases, run | `RunDefinitions/RunStructure/` |
| Individual waves | `RunDefinitions/Waves/` |
| Enemy types | `Enemies/` |
| Gold + enemy scaling configs | `RunDefinitions/` |
---
## 1. Enemy-buff cards
Two asset layers, because network identity and phase gating are separate concerns.
**Abilities** (`TD/Enemy Abilities/…`) — what a buffed enemy *does*:
- Split On Death — already exists; **re-check its fields**, they changed. It now has
`SplitCount`, `HpPercent`, `SpeedPercent`, `ScalePercent`, `LivesCostPercent`,
`FlightHeightPercent`, `ScatterRadius`.
- Flight, Blink, No Bounty, Gold Theft — new, need assets creating.
**Cards** (`TD/Enemy Upgrades/…`) — what players vote on:
- One `Ability Card` per ability above, each pointing at its `EnemyAbilityDefinition`.
- One `Double Up Card` (vote-meta; grants no ability).
**Groups** (`TD/Enemy Upgrades/Upgrade Group`) — the unit of phase gating:
Cut groups by *when a card should become available*, not by what it does, since a group is what
you drag between phases: `EUG_Phase1_Basics`, `EUG_Phase2_Nasty`, and so on. Availability
accumulates — phase 2 lists both Basics and Nasty. For MVP, one group holding every card is fine.
> A card must be in **both** the pool (§4, network identity) and a group (phase gating) to ever be
> offered. Pooled-but-ungrouped is silently never offered — that's how you park an authored card
> you aren't ready to use. Grouped-but-unpooled logs a warning and is skipped.
>
> Keep a card in only **one** group per phase. Two referenced groups holding the same card give it
> two independent shots at the draw, effectively doubling its rate.
---
## 2. Run structure
Build bottom-up:
1. **`WaveGroup`** assets (`TD/Run/Wave Group`) — bags of weighted `WaveDefinition` entries.
Weights are **relative** 01 and default to 1 (equal odds). A zero weight warns inline and
means the wave can never be drawn.
2. **`PhaseDefinition`** (`TD/Run/Phase Definition`) — `WaveGroups`, `BossGroups`, and
`EnemyUpgradeGroups` (the groups authored in §1).
3. **`RunDefinition`** (`TD/Run/Run Definition`) — `WavesPerCycle` (5), `CyclesPerPhase` (3),
and the `Phases` array. **MVP needs one phase.**
The `RunDefinition` inspector shows a live validation panel and a per-phase capacity readout.
Get it green before pressing play.
### The constraint that will bite
A phase must supply **`WavesPerCycle` waves with DISTINCT enemy types**. Enemy buffs are keyed to
a wave slot, so two slots sharing an enemy type would make "which wave did this buff apply to"
ambiguous. Ten waves in a pool is *not* enough if they only use three enemies. The inspector
reports distinct-type count per phase; the draw fails loudly at match start otherwise.
Ten wave definitions and ten enemy definitions already exist, so the content floor is met — but
check the type spread.
---
## 3. Scene objects (both `9Player` and `Main`)
Everything these reference now exists. Two new GameObjects need a `NetworkObject`; one doesn't.
| GameObject | Component | NetworkObject? | Assign |
|---|---|---|---|
| `RunState` | `RunState` | **Yes** | The `RunDefinition` asset from §2. |
| `WaveVote` | `WaveVote` | **Yes** | Nothing — `Options Per Vote` defaults to 3. |
| `EnemyUpgradePool` | `EnemyUpgradePool` | No | Every **card** asset from §1 into `options`. |
Also update the pool that already exists:
- **`EnemyAbilityPool`** — add every **ability** asset from §1 to its `abilities` array. Its
`noAbilityWeight` field is **gone** (abilities are voted in now, not rolled per enemy).
`WaveManager` **no longer has a `waveDefinitions` array** — that field is gone. Its two new
serialized fields are `Draft Time Seconds` (30) and `Vote Time Seconds` (25).
> Both new NetworkBehaviours are scene objects, so they must be present *before* the host starts.
>
> `EnemyUpgradePool.options` array order **is** each card's network id. Appending is safe;
> inserting or reordering desyncs a host from clients running a different build.
---
## 4. Gold config
`GoldConfig.Waves` is now **`GoldConfig.Encounters`**, indexed by global encounter number and
counted continuously across the whole run — cycles do **not** reset it. One phase of
5 waves × 3 cycles + a boss needs **16 entries**. Missing entries pay zero for that encounter.
Entries no longer reference a `WaveDefinition`. Payout is a property of *run position*, not of
which wave was drawn — encounter 3 pays encounter 3's rates whatever spawns in it. The inspector
labels each element `Encounter N` and projects per-encounter and cumulative earnings against
`PreviewEnemiesPerEncounter`, a preview-only figure that nothing at runtime reads.
> The existing asset's `Waves` data migrates automatically on first import
> (`[FormerlySerializedAs]`) — but it holds 10 entries and one phase needs 16, so the last six
> encounters (including the boss) pay nothing until they're authored.
---
## 5. Enemy scaling
Create an **`EnemyScalingConfig`** (`TD/Enemy Scaling Config`) and assign it to `WaveManager`
alongside the `GoldConfig`. Without it every enemy spawns at a flat 100 HP and difficulty never
progresses — the run is playable but meaningless.
Three numbers, no per-encounter table:
| Field | Default | Meaning |
|---|---|---|
| `BaseHpBudget` | 5000 | Total enemy health encounter 1 presents, **per player zone** |
| `GrowthPerEncounter` | 1.18 | +18% per encounter → budget doubles roughly every 4 |
| `BossHpMultiplier` | 6 | Extra multiplier on a boss encounter |
**Health is a budget, divided.** Per-enemy HP is the encounter's budget split across the wave's
enemy count, then skewed by the type's `HpMultiplier`. This is what lets waves be drawn at random:
a 150-strong swarm and an 8-strong elite pack in the same slot present the same total threat.
Enemy count becomes a *texture* knob, not a difficulty one.
> **`EnemyDefinition.MaxHp` no longer exists.** It's `HpMultiplier` now — a deviation from budget,
> not hit points. All ten definitions have been re-authored into a 0.71.35 band by archetype
> (golems tanky/slow, ents fragile/quick, drake fastest and flimsiest). **These are placeholder
> values — tune them.**
>
> `MoveSpeed` stays absolute and is deliberately never scaled by run progress. Escalating speed
> compounds with escalating health and invalidates tower balance mid-run; fast-vs-slow is a fixed
> character trait. Escalation is carried by health and the voted buffs.
---
## 6. Player prefab
Add **`PlayerTowerUpgrades`** to `Player.prefab`, alongside `PlayerTowerDeck` / `PlayerDraft`.
Without it, tower-upgrade draft options can't apply and the Upgrade buttons never appear.
---
## 7. Enemy prefabs
Any enemy that should be able to carry wave buffs needs an **`EnemyAbility`** component on its
root. `WaveManager` logs a warning naming the prefab if a buffed wave spawns an enemy without one.
`SplitOnDeath` and any size-changing buff need the prefab's **`NetworkTransform` to sync scale**,
or minions render full-size on remote peers.
---
## 8. Tower upgrade trees
1. Author upgraded towers as ordinary `TowerDefinition` assets and add them to
`TowerPlacementManager.towerDefinitions` (**not** to `startingDeck`).
2. Wire the tree by filling each parent's `UpgradePaths` array with its children.
3. Each upgrade node's **`GoldCost` is its upgrade price** — upgrade nodes are never placed
directly, so the field is free to mean that.
4. **Footprint must match the parent.** A differently-sized upgrade is rejected: the tower's
occupied/unwalkable tiles are stamped at its current size, and converting would leave the grid
describing a shape the tower no longer has.
5. Create a `TD/Draft/Tower Upgrade Option` per node (`BaseTower``UpgradedTower`) and add it to
`DraftPool`. Prerequisites need no explicit list — a node is offered only once its parent is
reachable (in the deck, or itself unlocked).
Existing `TowerUpgradeDraftOption` assets **changed meaning**: they used to swap the deck entry,
they now unlock an upgrade node. Re-check any that exist.
---
## Phase boundaries reset the board
When a boss dies and a new phase is drawn, **every built tower is destroyed with no refund**
players rebuild their maze from scratch each phase. Nothing to wire; it runs automatically off
`RunAdvance.NextPhase`.
- Queued and in-progress builds **are** refunded — those towers were never delivered, so eating
the gold would just punish having a build queued when the boss happened to die.
- Shelved build sites are cleared too, so no ghosts outlive the maze.
- A system message names the loss in chat, since an entire maze vanishing needs a stated cause.
- The final phase's boss ends the run instead, so no wipe happens there.
---
## Known gaps
- **Upgraded towers keep the parent's mesh.** Switching the definition changes stats immediately
(`TowerCombat` re-reads it every tick) but not the model — visual swap is an art task.
- **No 3-player map yet.** `MatchRules.MaxPlayers` is 3 and slot allocation caps there, but
`9Player` is still the only authored level.
- **Tower vulnerability is not built** — Savage, Mind Control, AOE EMP, Fart Miasma and Dummy all
need towers to be damageable/disableable, which doesn't exist. Deferred by agreement.
- **Relics** untouched, as agreed.