diff --git a/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs b/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs
index 83aec06..b809684 100644
--- a/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs
+++ b/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs
@@ -68,8 +68,8 @@ namespace TD.Gameplay
"the prefab based on the player's RaceSelection.")]
public GameObject BuilderPrefab;
- [Tooltip("STUB (Phase 1.8): tower roster available to this race. TowerRegistry will " +
- "filter to this list when the active player belongs to this race.")]
+ [Tooltip("STUB: tower roster available to this race/builder. The draft pool or " +
+ "starting deck will eventually filter to this list for this race.")]
public TowerDefinition[] Towers;
}
}
diff --git a/Assets/_Project/Scripts/Gameplay/TowerInstance.cs b/Assets/_Project/Scripts/Gameplay/TowerInstance.cs
index b99c0c2..724776e 100644
--- a/Assets/_Project/Scripts/Gameplay/TowerInstance.cs
+++ b/Assets/_Project/Scripts/Gameplay/TowerInstance.cs
@@ -1,5 +1,4 @@
// Assets/_Project/Scripts/Gameplay/TowerInstance.cs
-using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using TD.Core;
@@ -32,11 +31,11 @@ namespace TD.Gameplay
/// are idempotent writes, so double-stamping is safe.
///
/// Definition reference replication. TowerDefinition assets live in the
- /// project on all clients. We replicate the asset by name via a
- /// holding a FixedString64Bytes, then look
- /// up the asset locally. This avoids serializing the full ScriptableObject over the
- /// network. The lookup uses a singleton that must be
- /// present in the scene. (Temporary: will be driven by RaceDefinition in Path E.)
+ /// project on all clients. We replicate the tower's TowerTypeId (its index in
+ /// 's catalog) via a ,
+ /// then resolve the full asset locally with .
+ /// This avoids serializing the ScriptableObject over the network and reuses the same
+ /// identifier placement and the deck already use — a single source of truth.
///
/// Combat. No combat logic here yet. Combat fields live stubbed on
/// ; they will be consumed by a future
@@ -58,12 +57,13 @@ namespace TD.Gameplay
// ----- Networked state ------------------------------------------------
- // The name of the TowerDefinition asset for this tower. Replicated so all
- // clients can look up the full definition locally without sending the whole
- // ScriptableObject over the wire.
- private readonly NetworkVariable definitionName =
- new NetworkVariable(
- default,
+ // The TowerTypeId (index into TowerPlacementManager's catalog) for this tower.
+ // Replicated so every client resolves the full definition locally via the catalog —
+ // the same identifier placement and the deck already use. 0 = unset/invalid (the
+ // reserved catalog index). Replaces the old replicate-by-name + TowerRegistry path.
+ private readonly NetworkVariable definitionTypeId =
+ new NetworkVariable(
+ 0,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server);
@@ -95,8 +95,8 @@ namespace TD.Gameplay
// ----- Local resolved state -------------------------------------------
- // Resolved on every client in OnNetworkSpawn from definitionName.
- // Null if the lookup fails (missing TowerRegistry or unknown name).
+ // Resolved on every client in OnNetworkSpawn from definitionTypeId via the catalog.
+ // Null if the lookup fails (TypeId not in the catalog on this peer).
private TowerDefinition resolvedDefinition;
// ----- Pre-spawn initialization data ----------------------------------
@@ -192,7 +192,7 @@ namespace TD.Gameplay
hasPendingInit = true;
// Cache the resolved definition on the server immediately — clients
- // will resolve via the registry lookup once definitionName arrives.
+ // resolve from the replicated TowerTypeId via the catalog once it arrives.
resolvedDefinition = def;
}
@@ -206,7 +206,17 @@ namespace TD.Gameplay
// every client sees correct values on its very first OnNetworkSpawn.
if (IsServer && hasPendingInit)
{
- definitionName.Value = new FixedString64Bytes(pendingDefinition.name);
+ // Resolve the catalog index for this definition and replicate that.
+ int typeId = 0;
+ var pm = TowerPlacementManager.Instance;
+ if (pm == null || !pm.TryGetTypeId(pendingDefinition, out typeId))
+ {
+ Debug.LogError($"[TowerInstance] Could not resolve a TowerTypeId for " +
+ $"'{pendingDefinition?.name}'. Ensure it is in the " +
+ $"TowerPlacementManager catalog (towerDefinitions).");
+ }
+
+ definitionTypeId.Value = typeId;
anchorTile.Value = pendingAnchor;
ownerSlot.Value = pendingOwner;
@@ -331,27 +341,13 @@ namespace TD.Gameplay
// Already resolved (server path via InitializeServer).
if (resolvedDefinition != null) return;
- string defName = definitionName.Value.ToString();
- if (string.IsNullOrEmpty(defName))
- {
- Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
- $"definitionName is empty. Cannot resolve TowerDefinition.");
- return;
- }
-
- var registry = TowerRegistry.Instance;
- if (registry == null)
- {
- Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
- $"No TowerRegistry found in scene. Cannot resolve '{defName}'.");
- return;
- }
-
- resolvedDefinition = registry.Get(defName);
+ int typeId = definitionTypeId.Value;
+ resolvedDefinition = TowerPlacementManager.GetDefinition(typeId);
if (resolvedDefinition == null)
{
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
- $"TowerRegistry does not contain a definition named '{defName}'.");
+ $"no TowerDefinition for TypeId {typeId}. Is the " +
+ $"TowerPlacementManager catalog populated on this peer?");
}
}
diff --git a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs b/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs
deleted file mode 100644
index 389df11..0000000
--- a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-// Assets/_Project/Scripts/Gameplay/TowerRegistry.cs
-using System.Collections.Generic;
-using UnityEngine;
-using TD.Towers;
-
-namespace TD.Gameplay
-{
- ///
- /// Scene singleton that holds every available in the
- /// current match and lets any code look one up by asset name.
- ///
- ///
- /// Why this exists. replicates a tower's
- /// definition by name (a FixedString64Bytes over the network), then resolves
- /// the full ScriptableObject locally on every client. TowerRegistry is the lookup
- /// table that makes that resolution possible without hard-coding asset paths.
- ///
- /// Registration. Drag every asset into the
- /// Definitions list on this component in the inspector. Assets can live anywhere
- /// in the project — no special folder required.
- ///
- /// Path E upgrade path. In Path E the registry will filter to only the
- /// definitions belonging to the active match's RaceDefinition rosters. For now
- /// all assigned assets are registered.
- ///
- /// Plain MonoBehaviour. Not a NetworkBehaviour — the registry is
- /// identical on every peer (same assets, same names), so there is nothing to sync.
- ///
- public class TowerRegistry : MonoBehaviour
- {
- // ----- Singleton --------------------------------------------------
-
- ///
- /// The active TowerRegistry. Null before Awake or after the scene unloads.
- /// Always null-check before use.
- ///
- public static TowerRegistry Instance { get; private set; }
-
- // ----- Inspector --------------------------------------------------
-
- [Tooltip("All TowerDefinition assets available in this match. " +
- "Drag assets here from Assets/_Project/Data/TowerDefinitions/ " +
- "(or wherever they live). Asset name is used as the registry key.")]
- [SerializeField] private TowerDefinition[] definitions;
-
- // ----- Internal lookup table --------------------------------------
-
- // Keyed by TowerDefinition.name (the asset name, not DisplayName).
- private readonly Dictionary byName
- = new Dictionary();
-
- // ----- Lifecycle --------------------------------------------------
-
- private void Awake()
- {
- if (Instance != null && Instance != this)
- {
- Debug.LogError("[TowerRegistry] Multiple instances detected. " +
- "Only one TowerRegistry should exist per scene.");
- return;
- }
- Instance = this;
-
- BuildLookupTable();
- }
-
- private void OnDestroy()
- {
- if (Instance == this) Instance = null;
- }
-
- // ----- Public API -------------------------------------------------
-
- ///
- /// Returns the whose asset name equals
- /// , or null if no match is found.
- ///
- public TowerDefinition Get(string assetName)
- {
- byName.TryGetValue(assetName, out var def);
- return def;
- }
-
- ///
- /// Returns all registered tower definitions. Enumerates the internal
- /// dictionary values — do not modify the returned collection.
- ///
- public IEnumerable All => byName.Values;
-
- // ----- Private ----------------------------------------------------
-
- private void BuildLookupTable()
- {
- byName.Clear();
-
- if (definitions == null || definitions.Length == 0)
- {
- Debug.LogWarning("[TowerRegistry] No TowerDefinition assets assigned. " +
- "Drag assets into the Definitions list on the TowerRegistry component.");
- return;
- }
-
- foreach (var def in definitions)
- {
- if (def == null) continue;
-
- if (byName.ContainsKey(def.name))
- {
- Debug.LogWarning($"[TowerRegistry] Duplicate asset name '{def.name}'. " +
- $"Only the first entry will be used. Rename one of the assets.");
- continue;
- }
-
- byName[def.name] = def;
- }
-
- Debug.Log($"[TowerRegistry] Registered {byName.Count} tower definition(s).");
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs.meta b/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs.meta
deleted file mode 100644
index 6a7a87c..0000000
--- a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: a9dc0fbbe4422bc479ab8db7658c082b
\ No newline at end of file
diff --git a/Project_Context.md b/Project_Context.md
index 7242383..b9552bb 100644
--- a/Project_Context.md
+++ b/Project_Context.md
@@ -90,7 +90,7 @@ Unity **6.4 (6000.4.4f1)**, URP, IL2CPP, .NET Standard 2.1, Linear color space,
- **Visuals are placeholder** throughout (primitive/cone towers, sourced enemy models). See roadmap art TODOs.
- **Enemies lack idle animation support** — needs adding across all enemy prefabs.
- **Tower footprint visuals don't fill their 2×2 space** — the cone/mesh sits centered and reads smaller than the tile area it occupies; should visually reflect the full footprint.
-- **Two redundant tower lists:** `TowerPlacementManager.towerDefinitions[]` (by-index catalog) and `TowerRegistry.definitions[]` (by-name, for instance resolution). Candidate for consolidation.
+- **Tower catalog is now a single source of truth:** `TowerPlacementManager.towerDefinitions[]` (by-index, = `TowerTypeId`). `TowerInstance` replicates that index and resolves via `TowerPlacementManager.GetDefinition`. *(The old by-name `TowerRegistry` was removed 2026-06-24 — add new towers only to `towerDefinitions` + `DraftPool`.)*
- **Catalog index 0 is a reserved sentinel** (valid `TowerTypeId`s start at 1) — easy to forget when wiring the catalog in the inspector.
- **Catalog-index identifiers aren't session-stable** — blocks cross-match persistence until a stable ID is added.
- **Paint system frozen**; **Race vs Builder** naming unresolved; the gold **"Buy Roll available any time"** rule is provisional and may change.