## Game: Indicatrix (Working title)

The game Indicatrix (working title) is in the development stage. The programming language is C# and the game engine is Unity. I use AI tools to create the program code, but I wrote the game architecture completely myself. I don’t have a specific goal for the game yet, for now it is a game that appears to be a train and vehicle transport simulator. The exact specification of the game will develop over time. I program it myself, I revise and check the entire code on my own. I am not working on the graphics side of the game yet. I will probably use some commercial purchased graphic materials from publicly available sources.

TerrainManager.cs

using NUnit.Framework.Constraints;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;


public class TerrainManager : MonoBehaviour
{
    public static TerrainManager instance;
    public int terrainWidth, elementWidth;
    public TerrainElement terrainPrefab;
    [HideInInspector]
    public Vector3[] coordsF;
    public Vector3[] coordsI;
    [HideInInspector]
    public TerrainElement[] terrainElements;


    private const float baseHeight = 2.75f;
    private const float step = 0.25f;

    // =====================================================================
    // GLOBÁLNE VÝŠKOVÉ LIMITY TERÉNU
    //
    // Užívateľská "úroveň prevýšenia" E:
    //   E = -1 → Y = 2.75f (= baseHeight, najnižšia, "fiktívna" úroveň pod rovinou)
    //   E =  0 → Y = 3.00f (rovina terénu)
    //   E = 10 → Y = 5.50f (najvyššia povolená úroveň)
    // Prepočet výšky: Y = baseHeight + (E + 1) * step.
    //
    // Celá mapa (všetky vertexy) musí ostať v rozsahu [MinTerrainHeight,
    // MaxTerrainHeight]. Limity sú odvodené z úrovní, takže stačí zmeniť
    // Min/MaxElevationLevel a výšky sa prepočítajú.
    // =====================================================================

    public const int MinElevationLevel = -1; // Y = 2.75f
    public const int MaxElevationLevel = 10; // Y = 5.50f

    public const float MinTerrainHeight = baseHeight + (MinElevationLevel + 1) * step; // 2.75f
    public const float MaxTerrainHeight = baseHeight + (MaxElevationLevel + 1) * step; // 5.50f


    // =====================================================================
    // PARAMETRE GENEROVANIA TERÉNU (laditeľné v Inspectore)
    //
    // Hodnoty seaFloorLevel / maxLandLevel sú v užívateľskej úrovni E
    // (rovnaká škála ako vyššie: E=-1 = hladina vody, E=0 = rovina).
    // Vnútorne sa prepočítavajú na "coordsI" úroveň = E + 1.
    // =====================================================================

    [Header("Generovanie terénu")]
    [Tooltip("0 = pri každom spustení iný náhodný terén; iná hodnota = opakovateľný (rovnaký) terén.")]
    public int terrainSeed = 0;

    [Tooltip("Mierka šumu. Menšie číslo = väčšie a plynulejšie útvary (viac rovín); väčšie = členitejší terén.")]
    public float noiseScale = 0.03f;

    [Tooltip("Počet vrstiev šumu (detail). Viac = drobnejšie nerovnosti navrch.")]
    [Range(1, 8)]
    public int octaves = 4;

    [Tooltip("Útlm amplitúdy medzi vrstvami šumu (0..1). Nižšie = hladší terén.")]
    [Range(0f, 1f)]
    public float persistence = 0.5f;

    [Tooltip("Nárast frekvencie medzi vrstvami šumu (typicky 2).")]
    public float lacunarity = 2f;

    [Tooltip("Podiel mapy (0..1), ktorý spadne na vodu/pobrežie. Vyššie = viac vody.")]
    [Range(0f, 0.9f)]
    public float seaThreshold = 0.30f;

    [Tooltip("Najnižšia úroveň E dna pod vodou. Pod -1 = terén klesne pod hladinu a voda je viditeľná.")]
    public int seaFloorLevel = -2;

    [Tooltip("Najvyššia úroveň E pevniny (kopce). Max 10.")]
    public int maxLandLevel = 8;

    [Tooltip("Sploštenie nížin (mocnina). >1 = viac rovín a menej kopcov, =1 = lineárne.")]
    public float flatBias = 1.5f;


    /// <summary>
    /// Vráti true, ak by ďalší krok úpravy terénu na vrchole s aktuálnou výškou
    /// snapY prekročil povolený rozsah:
    ///   • levelUp == true  → nová výška (snapY + step) by presiahla MaxTerrainHeight,
    ///   • levelUp == false → nová výška (snapY - step) by klesla pod MinTerrainHeight.
    ///
    /// Stačí kontrolovať klikaný vrchol: pri LevelUp je práve on najvyšším a pri
    /// LevelDown najnižším bodom zmeny – kaskáda (TerrainCollapse) susedov len
    /// doťahuje smerom k nemu, takže limit neprekročí nič, čo neprekročí on.
    /// </summary>
    public bool WouldExceedElevationLimit(float snapY, bool levelUp)
    {
        const float EPS = 0.0001f;

        if (levelUp)
            return snapY + step > MaxTerrainHeight + EPS;
        else
            return snapY - step < MinTerrainHeight - EPS;
    }


    void Start()
    {
        instance = this;

        coordsF = CreateMap(3.00f);
        coordsI = CreateMap(0);

        // Náhodný terén ešte PRED postavením mesh-ov, aby sa už prvé
        // vykreslenie elementov postavilo z vygenerovaných výšok.
        GenerateRandomTerrain();

        CreateTerrainElements();
    }


    // =====================================================================
    // GENERÁTOR NÁHODNÉHO TERÉNU
    //
    // Postup (inšpirované OpenTTD/TTD – plynulé útvary, veľa rovín):
    //   1) fraktálny Perlin šum (fBm) → plynulé výškové pole 0..1
    //   2) mapovanie šumu na úroveň E s "hladinou" a sploštením nížin
    //   3) EnforceMaxSlope – dotiahnutie na pravidlo "susedia max o 1 úroveň"
    //      (rovnaké pravidlo ako TerrainCollapse v editore) → vznikajú
    //      prirodzené svahy a rovinné plató
    //   4) prepočet coordsI → coordsF (reálne Y výšky pre mesh)
    //
    // Vodná vrstva (child[0] v TerrainElement) sa NEMENÍ – ostáva na Y=2.75.
    // =====================================================================
    public void GenerateRandomTerrain()
    {
        if (coordsI == null || coordsF == null)
            return;

        int size = terrainWidth + 1;

        // Seed: 0 = nový náhodný pri každom spustení.
        if (terrainSeed == 0)
            UnityEngine.Random.InitState(Environment.TickCount);
        else
            UnityEngine.Random.InitState(terrainSeed);

        // Náhodný posun do šumového poľa → iná mapa pri každom behu.
        float offsetX = UnityEngine.Random.Range(-100000f, 100000f);
        float offsetZ = UnityEngine.Random.Range(-100000f, 100000f);

        // Bezpečné orezanie limitov (v úrovni E).
        int maxLandE = Mathf.Clamp(maxLandLevel, MinElevationLevel, MaxElevationLevel);
        int seaFloorE = Mathf.Clamp(seaFloorLevel, -5, maxLandE);

        for (int z = 0; z <= terrainWidth; z++)
        {
            for (int x = 0; x <= terrainWidth; x++)
            {
                int idx = z * size + x;

                // 1) fBm Perlin – súčet niekoľkých vrstiev šumu.
                float amp = 1f, freq = 1f, sum = 0f, ampSum = 0f;
                for (int o = 0; o < octaves; o++)
                {
                    float sx = offsetX + x * noiseScale * freq;
                    float sz = offsetZ + z * noiseScale * freq;
                    sum += amp * Mathf.PerlinNoise(sx, sz);
                    ampSum += amp;
                    amp *= persistence;
                    freq *= lacunarity;
                }
                float n = (ampSum > 0f) ? Mathf.Clamp01(sum / ampSum) : 0f;

                // 2) Mapovanie šumu → úroveň E.
                float e;
                if (n < seaThreshold)
                {
                    // Voda/pobrežie: od dna (seaFloorE) po hladinu (E=-1).
                    float t = (seaThreshold > 0f) ? n / seaThreshold : 0f;
                    e = Mathf.Lerp(seaFloorE, MinElevationLevel, t);
                }
                else
                {
                    // Pevnina: od roviny (E=0) nahor, so sploštením nížin.
                    float t = (n - seaThreshold) / (1f - seaThreshold);
                    t = Mathf.Pow(t, Mathf.Max(0.01f, flatBias));
                    e = Mathf.Lerp(0f, maxLandE, t);
                }

                int eRounded = Mathf.Clamp(Mathf.RoundToInt(e), seaFloorE, maxLandE);

                // coordsI úroveň = E + 1 (rovnaký vzťah ako ConvertCoordsFI).
                coordsI[idx].x = x;
                coordsI[idx].z = z;
                coordsI[idx].y = eRounded + 1;
            }
        }

        // 3) Dotiahnutie na max sklon 1 úrovne medzi susedmi (TT pravidlo).
        EnforceMaxSlope(coordsI);

        // 4) Prepočet na reálne Y výšky používané mesh-om.
        ConvertCoordsFI(false);
    }


    /// <summary>
    /// Zabezpečí, že žiadne dva susedné vrcholy sa nelíšia o viac než 1 úroveň
    /// (rovnaké pravidlo ako TerrainCollapse). Pracuje "iba znižovaním":
    /// každý vrchol môže byť nanajvýš o 1 nad svojím NAJNIŽŠÍM susedom, inak sa
    /// zníži. Tým sa zachovajú nížiny/voda a zo strmých vrcholov vzniknú plynulé
    /// svahy a rovinné plató (presne TT/OpenTTD vzhľad). Postup je monotónne
    /// klesajúci a celočíselný, takže vždy skonverguje.
    /// </summary>
    private void EnforceMaxSlope(Vector3[] ci)
    {
        int size = terrainWidth + 1;

        bool changed = true;
        while (changed)
        {
            changed = false;

            for (int z = 0; z <= terrainWidth; z++)
            {
                for (int x = 0; x <= terrainWidth; x++)
                {
                    int idx = z * size + x;
                    float h = ci[idx].y;

                    float minN = float.MaxValue;
                    if (x > 0) minN = Mathf.Min(minN, ci[idx - 1].y);
                    if (x < terrainWidth) minN = Mathf.Min(minN, ci[idx + 1].y);
                    if (z > 0) minN = Mathf.Min(minN, ci[idx - size].y);
                    if (z < terrainWidth) minN = Mathf.Min(minN, ci[idx + size].y);

                    if (h > minN + 1f)
                    {
                        ci[idx].y = minN + 1f;
                        changed = true;
                    }
                }
            }
        }
    }



    public void TerrainCollapse(int startIndex)
    {
        // Reálny beh nad živým coordsI (pôvodné správanie zachované).
        TerrainCollapse(startIndex, coordsI);
    }

    /// <summary>
    /// Jadro collapse algoritmu parametrizované poľom integer-výšok (ci).
    /// Volá sa buď nad reálnym coordsI, alebo nad pracovnou KÓPIOU pri
    /// simulácii (PredictVertexLevelChanges) – vďaka tomu sa logika
    /// nedupluje a simulácia presne zodpovedá reálnemu výsledku.
    /// </summary>
    private void TerrainCollapse(int startIndex, Vector3[] ci)
    {
        int size = terrainWidth + 1;

        Queue<int> queue = new Queue<int>();
        HashSet<int> visited = new HashSet<int>();

        queue.Enqueue(startIndex);
        visited.Add(startIndex);

        while (queue.Count > 0)
        {
            int currentIndex = queue.Dequeue();

            Vector3 current = ci[currentIndex];
            int cx = (int)current.x;
            int cz = (int)current.z;
            float currentHeight = current.y;

            Vector2Int[] directions =
            {
            new Vector2Int( 1, 0),
            new Vector2Int(-1, 0),
            new Vector2Int( 0, 1),
            new Vector2Int( 0,-1)
        };

            foreach (var dir in directions)
            {
                int nx = cx + dir.x;
                int nz = cz + dir.y;

                if (nx < 0 || nz < 0 || nx > terrainWidth || nz > terrainWidth)
                    continue;

                int neighborIndex = nz * size + nx;

                float neighborHeight = ci[neighborIndex].y;
                float delta = Mathf.Abs(currentHeight - neighborHeight);

                if (delta > 1)
                {
                    if (neighborHeight < currentHeight)
                        ci[neighborIndex].y = currentHeight - 1;
                    else
                        ci[neighborIndex].y = currentHeight + 1;

                    if (!visited.Contains(neighborIndex))
                    {
                        queue.Enqueue(neighborIndex);
                        visited.Add(neighborIndex);
                    }
                }
            }
        }
    }




    public void TerrainVertexLevel(float snapX, float snapY, float snapZ, bool levelUp)
    {
        int coordClick = 0;

        int size = terrainWidth + 1;

        int centerX = Mathf.RoundToInt(snapX);
        int centerZ = Mathf.RoundToInt(snapZ);

        int minX = Mathf.Max(0, centerX - 10);
        int maxX = Mathf.Min(terrainWidth, centerX + 10);

        int minZ = Mathf.Max(0, centerZ - 10);
        int maxZ = Mathf.Min(terrainWidth, centerZ + 10);

        for (int z = minZ; z <= maxZ; z++)
        {
            for (int x = minX; x <= maxX; x++)
            {
                int i = z * size + x;

                if (coordsF[i].x == snapX && coordsF[i].y == snapY && coordsF[i].z == snapZ)
                {
                    coordsF[i].y += levelUp ? 0.25f : -0.25f;
                    coordClick = i;
                }
            }
        }

        ConvertCoordsFI(true);
        TerrainCollapse(coordClick);
        ConvertCoordsFI(false);

        UpdateRegion(coordClick);
    }


    /// <summary>
    /// SIMULÁCIA (dry-run) operácie TerrainVertexLevel BEZ akejkoľvek zmeny
    /// reálneho terénu. Vráti grid-súradnice [vx,vz] VŠETKÝCH vrcholov, ktorých
    /// výška Y by sa po klikoch (vrátane kaskádového TerrainCollapse) zmenila.
    ///
    /// PREČO: úprava jedného vrcholu môže cez TerrainCollapse posunúť aj mnoho
    /// okolitých vrcholov (aby susedné výškové úrovne nelíšili o viac než 1).
    /// Volajúci (GameManager) tak vie ešte PRED reálnou úpravou skontrolovať,
    /// či by sa niektorý z dotknutých vrcholov dotkol obsadeného tile, a ak áno,
    /// operáciu vôbec nespustiť.
    ///
    /// Celý výpočet beží nad KÓPIAMI coordsF/coordsI – živé dáta ostávajú
    /// nedotknuté a mesh sa neprestavuje.
    /// </summary>
    public List<Vector2Int> PredictVertexLevelChanges(float snapX, float snapY, float snapZ, bool levelUp)
    {
        var changed = new List<Vector2Int>();

        if (coordsF == null || coordsI == null)
            return changed;

        int size = terrainWidth + 1;

        // Pracovné kópie – reálny terén nemodifikujeme.
        Vector3[] simF = (Vector3[])coordsF.Clone();
        Vector3[] simI = (Vector3[])coordsI.Clone();

        // 1) Rovnaké vyhľadanie klikaného vrcholu ako v TerrainVertexLevel.
        int coordClick = 0;

        int centerX = Mathf.RoundToInt(snapX);
        int centerZ = Mathf.RoundToInt(snapZ);

        int minX = Mathf.Max(0, centerX - 10);
        int maxX = Mathf.Min(terrainWidth, centerX + 10);

        int minZ = Mathf.Max(0, centerZ - 10);
        int maxZ = Mathf.Min(terrainWidth, centerZ + 10);

        for (int z = minZ; z <= maxZ; z++)
        {
            for (int x = minX; x <= maxX; x++)
            {
                int i = z * size + x;

                if (simF[i].x == snapX && simF[i].y == snapY && simF[i].z == snapZ)
                {
                    simF[i].y += levelUp ? 0.25f : -0.25f;
                    coordClick = i;
                }
            }
        }

        // 2) F→I, kaskádový collapse, I→F – všetko nad kópiami.
        ConvertCoordsFI(true, simF, simI);
        TerrainCollapse(coordClick, simI);
        ConvertCoordsFI(false, simF, simI);

        // 3) Pozbierame vrcholy, ktorých Y sa oproti živému stavu zmenilo.
        for (int i = 0; i < coordsF.Length; i++)
        {
            if (!Mathf.Approximately(simF[i].y, coordsF[i].y))
                changed.Add(new Vector2Int(i % size, i / size));
        }

        return changed;
    }





    private Vector3[] CreateMap(float initialHeight)
    {
        int size = (terrainWidth + 1) * (terrainWidth + 1);
        Vector3[] map = new Vector3[size];

        int i = 0;
        for (int z = 0; z <= terrainWidth; z++)
        {
            for (int x = 0; x <= terrainWidth; x++)
            {
                map[i++] = new Vector3(x, initialHeight, z);
            }
        }

        return map;
    }




    public void ConvertCoordsFI(bool floatToInt)
    {
        // Reálna konverzia nad živými poliami (pôvodné správanie).
        ConvertCoordsFI(floatToInt, coordsF, coordsI);
    }

    /// <summary>
    /// Konverzia float↔int výšok parametrizovaná poľami – aby ju vedela
    /// použiť aj simulácia (PredictVertexLevelChanges) nad kópiami.
    /// </summary>
    private void ConvertCoordsFI(bool floatToInt, Vector3[] f, Vector3[] iArr)
    {
        for (int i = 0; i < f.Length; i++)
        {
            if (floatToInt)
            {
                iArr[i].x = f[i].x;
                iArr[i].z = f[i].z;

                iArr[i].y = Mathf.RoundToInt((f[i].y - baseHeight) / step);
            }
            else
            {
                f[i].x = iArr[i].x;
                f[i].z = iArr[i].z;

                f[i].y = baseHeight + iArr[i].y * step;
            }
        }
    }




    private void CreateTerrainElements()
    {
        int tilesPerSide = terrainWidth / elementWidth;
        terrainElements = new TerrainElement[tilesPerSide * tilesPerSide];

        for (int i = 0, z = 0; z < tilesPerSide; z++)
        {
            for (int x = 0; x < tilesPerSide; x++, i++)
            {
                TerrainElement elementInstance = Instantiate(terrainPrefab, this.transform);
                elementInstance.Initialize(x, z);
                terrainElements[i] = elementInstance;
            }
        }
    }



    private void UpdateRegion(int centerIndex)
    {
        Vector3 center = coordsI[centerIndex];

        int minX = Mathf.Max(0, (int)center.x - 10);
        int maxX = Mathf.Min(terrainWidth, (int)center.x + 10);

        int minZ = Mathf.Max(0, (int)center.z - 10);
        int maxZ = Mathf.Min(terrainWidth, (int)center.z + 10);

        int elementSize = elementWidth;
        int tilesPerSide = terrainWidth / elementSize;

        HashSet<int> elementsToUpdate = new HashSet<int>();

        for (int z = minZ; z <= maxZ; z++)
        {
            for (int x = minX; x <= maxX; x++)
            {
                int elementX = x / elementSize;
                int elementZ = z / elementSize;

                int elementIndex = elementZ * tilesPerSide + elementX;

                elementsToUpdate.Add(elementIndex);
            }
        }

        foreach (int i in elementsToUpdate)
        {
            terrainElements[i].Rebuild();
        }
    }

}

TerrainElement.cs

using System;
using Unity.VisualScripting;
using UnityEngine;


[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class TerrainElement : MonoBehaviour
{
    private Mesh mesh;
    private MeshFilter meshFilter;
    private Vector3[] verts;
    private Color[] colors;
    private int[] tris;
    private Vector2[] uvs;

    private int indexX;
    private int indexZ;



    private bool TriangulationCheck(Vector3 coord0, Vector3 coord1)
    {
        if (coord0.y == coord1.y)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    private Color VertexColor(float height)
    {
        if (height < 1.5) //1.5
        {
            return Color.black;
        }
        else if (height < 3.5) //3.5
        {
            return Color.red;
        }
        else
        {
            return Color.green;
        }
    }



    public void Initialize(int index_x, int index_z)
    {
        indexX = index_x;
        indexZ = index_z;

        this.name = index_x + "_" + index_z;

        meshFilter = GetComponent<MeshFilter>();
        mesh = new Mesh();
        meshFilter.mesh = mesh;

        BuildMesh();
    }


    public void BuildMesh()
    {
        //getting width of element and terrain
        int width = TerrainManager.instance.elementWidth;
        int tWidth = TerrainManager.instance.terrainWidth;

        //setting the water position
        this.transform.GetChild(0).transform.position = new Vector3(indexX * width + width / 2, 2.75f, indexZ * width + width / 2);

        verts = new Vector3[width * width * 6];
        colors = new Color[verts.Length];
        uvs = new Vector2[verts.Length];
        tris = new int[verts.Length];

        //pivot point inside of the coord array
        int origin = indexX * width + indexZ * width * (tWidth + 1);

        for (int i = 0, z = 0; z < width; z++)
        {
            //outer loop for z-axis
            for (int x = 0; x < width; x++, i += 6)
            {
                //setting verts                    
                verts[i] = TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z)];
                verts[i + 1] = TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z)];
                verts[i + 2] = TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z + 1)];
                verts[i + 3] = TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z + 1)];

                colors[i] = VertexColor(TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z)].y);
                colors[i + 1] = VertexColor(TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z)].y);
                colors[i + 2] = VertexColor(TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z + 1)].y);
                colors[i + 3] = VertexColor(TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z + 1)].y);



                if (TriangulationCheck(TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z)], TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z + 1)]))
                {
                    //setting extra vertices
                    verts[i + 4] = TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z)];
                    verts[i + 5] = TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z + 1)];

                    //setting vertex colors
                    colors[i + 4] = VertexColor(TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z)].y);
                    colors[i + 5] = VertexColor(TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z + 1)].y);

                    //setting tris
                    tris[i] = i;
                    tris[i + 1] = i + 1;
                    tris[i + 2] = i + 2;
                    tris[i + 3] = i + 4;
                    tris[i + 4] = i + 5;
                    tris[i + 5] = i + 3;
                    //setting uvs
                    uvs[i] = new Vector2(0, 0);
                    uvs[i + 1] = new Vector2(0, 1);
                    uvs[i + 2] = new Vector2(1, 1);
                    uvs[i + 3] = new Vector2(1, 0);
                    uvs[i + 4] = new Vector2(0, 0);
                    uvs[i + 5] = new Vector2(1, 1);
                }

                else
                {
                    //setting extra vertices
                    verts[i + 4] = TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z + 1)];
                    verts[i + 5] = TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z)];

                    //setting vertex colors
                    colors[i + 4] = VertexColor(TerrainManager.instance.coordsF[origin + (x * (tWidth + 1) + z + 1)].y);
                    colors[i + 5] = VertexColor(TerrainManager.instance.coordsF[origin + ((x + 1) * (tWidth + 1) + z)].y);

                    //setting tris
                    tris[i] = i;
                    tris[i + 1] = i + 1;
                    tris[i + 2] = i + 3;
                    tris[i + 3] = i + 4;
                    tris[i + 4] = i + 5;
                    tris[i + 5] = i + 2;
                    //setting uvs
                    uvs[i] = new Vector2(0, 0);
                    uvs[i + 1] = new Vector2(0, 1);
                    uvs[i + 2] = new Vector2(1, 1);
                    uvs[i + 3] = new Vector2(1, 0);
                    uvs[i + 4] = new Vector2(1, 0);
                    uvs[i + 5] = new Vector2(0, 1);
                }


            }
        }

        mesh.vertices = verts;
        mesh.colors = colors;
        mesh.uv = uvs;
        mesh.triangles = tris;
        mesh.RecalculateNormals();

        this.AddComponent<MeshCollider>();
    }


    public void Rebuild()
    {
        mesh.Clear();
        BuildMesh();
    }



}

FactorySystem.cs

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// FactorySystem
/// ------------------------------------------------------------------
/// Dátová vrstva pre továrne. Doteraz boli továrne reprezentované iba ako
/// skupina tilov na mape (IndicatrixAPI.SetTile zapíše tileID 4/5 + stateID
/// = FactoryConstructionMode do footprintu). To stačí na VYKRESLENIE továrne,
/// ale nenesie to žiadne "ekonomické" informácie – koľko suroviny továreň
/// prijíma, vydáva, aké má kapacity.
///
/// Tento súbor dopĺňa práve tieto informácie a NEZASAHUJE do tile enginu:
///   - ResourceType      – evidenčné čísla surovín (0/1/2, rozšíriteľné).
///   - ResourceSlot      – jeden riadok [typ, aktuálne množstvo, max množstvo].
///   - FactoryDefinition – nemenná ŠABLÓNA jedného typu továrne
///                         (Name + Load/UnLoad sloty + odkaz na footprint).
///   - FactoryDatabase   – 4 fixné definície, indexované cez
///                         GameManager.FactoryConstructionMode (rovnaký vzor
///                         ako IndicatrixAPI.GetFactoryFootprint).
///   - FactoryInstance   – konkrétna POLOŽENÁ továreň na mape: vlastná kópia
///                         slotov (množstvá sa počas hry menia) + pozícia.
///
/// PREČO List<ResourceSlot> A NIE Hashtable / Dictionary:
///   Zadanie hovorí, že továren môže prijať/vydať 0, 1 alebo VIAC surovín
///   a uvádza zápis "Load: [2,0,500], [3,0,750]". Dictionary<ResourceType,…>
///   by znemožnil dva sloty s rovnakou surovinou a pridáva réžiu hashovania
///   bez úžitku (slotov je málo, 0–niekoľko). List<ResourceSlot> presne
///   kopíruje zápis zo zadania, podporuje 0/1/N slotov a je triviálne
///   serializovateľný pre Unity Inspector.
/// ------------------------------------------------------------------
/// </summary>

// ------------------------------------------------------------------
// SUROVINY
// ------------------------------------------------------------------

/// <summary>
/// Typ suroviny. Hodnota enumu = "evidenčné číslo" zo zadania, takže sa dá
/// priamo (int)ResourceType použiť ako ID a opačne (ResourceType)id naspäť.
///
/// Tabuľka surovín (zadanie):
///   0  = None                 (žiadna surovina)
///   1  = Coal                 (uhlie)
///   2  = Wood                 (drevo)
///   3  = IronOre              (železná ruda)
///   4  = Gold                 (zlatá ruda)
///   5  = Silver               (strieborná ruda)
///   6  = Livestock            (dobytok)
///   7  = Grain                (pšenica)
///   8  = Oil                  (ropa)
///   9  = Boards               (dosky)
///   10 = Plastic              (plast)
///   11 = Meat                 (mäso)
///   12 = Flour                (múka)
///   13 = Metals               (kovy)
///   14 = Glass                (sklo)
///   15 = Furniture            (nábytok)
///   16 = ElectronicsProducts  (elektronické produkty)
///
/// Rozšírenie: budúcu surovinu (ďalšie evidenčné číslo) stačí dopísať sem.
/// </summary>
public enum ResourceType
{
    None = 0,    // žiadna surovina
    Coal = 1,    // uhlie
    Wood = 2,    // drevo
    IronOre = 3,    // železná ruda
    Gold = 4,    // zlatá ruda
    Silver = 5,    // strieborná ruda
    Livestock = 6,    // dobytok
    Grain = 7,    // pšenica
    Oil = 8,    // ropa
    Boards = 9,    // dosky
    Plastic = 10,   // plast
    Meat = 11,   // mäso
    Flour = 12,   // múka
    Metals = 13,   // kovy
    Glass = 14,   // sklo
    Furniture = 15,   // nábytok
    ElectronicsProducts = 16    // elektronické produkty
}

/// <summary>
/// ResourceSlot
/// ------------------------------------------------------------------
/// Jeden riadok zo zadania: [typ suroviny, aktuálne množstvo, maximálne
/// množstvo] – t.j. [Type, Amount, Capacity].
///
/// Používa sa rovnako v zozname Load (príjem) aj UnLoad (výdaj). Jedna
/// továreň môže mať takýchto slotov 0, 1 alebo viac (pozri FactoryDefinition).
///
/// POZN.: Trieda (nie struct) je zvolená zámerne – FactoryInstance si musí
/// drža? MENITEĽNÉ množstvá (Amount sa počas hry mení nakladaním/vykladaním
/// vlakov). Reference type uľahčuje úpravu cez List bez index-prepisovania.
/// </summary>
[System.Serializable]
public class ResourceSlot
{
    /// <summary>Typ suroviny tohto slotu (Coal / Wood / …).</summary>
    public ResourceType type;

    /// <summary>Aktuálne množstvo suroviny v slote (0 .. capacity).</summary>
    public int amount;

    /// <summary>Maximálne množstvo suroviny, ktoré sa do slotu zmestí.</summary>
    public int capacity;

    public ResourceSlot(ResourceType type, int amount, int capacity)
    {
        this.type = type;
        this.amount = amount;
        this.capacity = capacity;
    }

    /// <summary>Hlboká kópia slotu – pri vytváraní FactoryInstance z definície,
    /// aby inštancia nezdielala menené množstvá so šablónou.</summary>
    public ResourceSlot Clone()
    {
        return new ResourceSlot(type, amount, capacity);
    }

    // Pomocné dotazy / operácie ---------------------------------------------------------------------------

    /// <summary>Voľné miesto v slote (koľko ešte možno pridať).</summary>
    public int FreeSpace => Mathf.Max(0, capacity - amount);

    public bool IsFull => amount >= capacity;
    public bool IsEmpty => amount <= 0;

    /// <summary>
    /// Pridá do slotu maximálne <paramref name="requested"/> jednotiek,
    /// orezané kapacitou. Vráti, koľko sa SKUTOČNE pridaťo.
    /// (Použiteľné napr. keď vlak/továreň "naplní" UnLoad sklad.)
    /// </summary>
    public int Add(int requested)
    {
        int added = Mathf.Clamp(requested, 0, FreeSpace);
        amount += added;
        return added;
    }

    /// <summary>
    /// Odoberie zo slotu maximálne <paramref name="requested"/> jednotiek,
    /// orezané dostupným množstvom. Vráti, koľko sa SKUTOČNE odobralo.
    /// (Použiteľné napr. keď vlak "naloží" zo skladu továrne.)
    /// </summary>
    public int Remove(int requested)
    {
        int removed = Mathf.Clamp(requested, 0, amount);
        amount -= removed;
        return removed;
    }

    public override string ToString() => $"[{(int)type} {type}, {amount}/{capacity}]";
}

// ------------------------------------------------------------------
// DEFINÍCIA TOVÁRNE (nemenná šablóna)
// ------------------------------------------------------------------

/// <summary>
/// FactoryDefinition
/// ------------------------------------------------------------------
/// Nemenná ŠABLÓNA jedného typu továrne – "vzor", podľa ktorého sa pri
/// položení vytvorí konkrétna FactoryInstance.
///
/// Obsahuje:
///   - Name   : názov továrne (string), napr. "Coal Mine".
///   - Mode   : ku ktorému FactoryConstructionMode definícia patrí
///              (prepojenie na GameManager / IndicatrixAPI / UI).
///   - TileID : 4 = Factory, 5 = Processing – zhodné s tým, čo posiela
///              GameManager do IndicatrixAPI.SetTile.
///   - Load   : zoznam slotov, ktoré továreň PRIJÍMA (0, 1 alebo N).
///   - UnLoad : zoznam slotov, ktoré továreň VYDÁVA (0, 1 alebo N).
///   - MapColor : farba, ktorou sa táto továreň kreslí do STATUS MAPY
///              (StatusMapMenuUI → IndustryView). Je to ČISTO mapová,
///              vnútorná reprezentácia – herná textúra továrne ani jej
///              vzhľad v hre sa NEMENÍ. Každý typ továrne má vlastnú,
///              odlišnú farbu, aby boli na prehľadovej mape rozlíšiteľné.
///
/// "Load: 0" zo zadania = prázdny zoznam (Count == 0).
/// "Load: [1,0,300]"    = zoznam s jedným ResourceSlot(Coal, 0, 300).
/// "Load: [2,0,500],[3,0,750]" = zoznam s dvoma slotmi (budúce rozšírenie).
///
/// FOOTPRINT zostáva definovaný v IndicatrixAPI.GetFactoryFootprint(Mode) –
/// FactorySystem ho ZÁMERNE neduplikuje, len naň cez Mode odkazuje, aby
/// veľkosti továrne mali aj naňalej jediný zdroj pravdy.
/// </summary>
[System.Serializable]
public class FactoryDefinition
{
    /// <summary>
    /// FIXNÉ číselné ID typu továrne. Hodnota je pridelená v PORADÍ, v akom
    /// továrne nasledujú v FactoryDatabase (1. Coal Mine = 0, 2. Forest = 1,
    /// … 16. Glass Factory = 15) a počas hry sa NEMENÍ. Slúži ako stabilný
    /// kľúč typu (napr. pre uloženie hry, štatistiky, UI). Každá položená
    /// FactoryInstance si toto ID prevezme do svojho rovnomenného poľa.
    /// </summary>
    public int ID;

    public string Name;
    public GameManager.FactoryConstructionMode Mode;
    public int TileID;                       // 4 = Factory, 5 = Processing

    public List<ResourceSlot> Load;          // príjem surovín  (0..N slotov)
    public List<ResourceSlot> UnLoad;        // výdaj surovín   (0..N slotov)

    /// <summary>
    /// Farba pre vykreslenie tejto továrne do STATUS MAPY (IndustryView).
    /// LEN pre mapový účel – nemení hernú textúru ani vzhľad v hre.
    /// </summary>
    public Color MapColor;

    /// <summary>
    /// Čas výstavby tejto továrne v SEKUNDÁCH (definované per-typ v kóde).
    /// Po položení beží odpočet a kým progres &lt; 100 %, je továreň "vo
    /// výstavbe" – zablokovaná pre zmenu množstiev/kapacít aj pre demoláciu.
    /// Hodnota &lt;= 0 znamená, že továreň je hotová okamžite (bez výstavby).
    /// </summary>
    public float BuildingTime;

    /// <summary>
    /// Cena (náklad) na postavenie tejto továrne v kreditoch (CR). Odpočíta sa
    /// z konta (GameEconomy.Balance) HNEĎ pri položení továrne – hráč nemusí
    /// čakať na dokončenie výstavby (100 %). Pri demolácii sa vráti 50 % z tejto
    /// sumy (rovnaká politika ako pri RAIL/ROAD – pozri ConstructionCosts).
    ///
    /// Centrálne sa cena číta cez ConstructionCosts.FactoryBuildCost(mode),
    /// ktorá ju berie práve odtiaľto – hodnota Cost je teda jediný zdroj pravdy.
    /// Hodnota &lt;= 0 znamená "zadarmo".
    /// </summary>
    public int Cost;

    public FactoryDefinition(
        int id,
        string name,
        GameManager.FactoryConstructionMode mode,
        int tileID,
        List<ResourceSlot> load,
        List<ResourceSlot> unload,
        Color mapColor,
        float buildingTime,
        int cost)
    {
        ID = id;
        Name = name;
        Mode = mode;
        TileID = tileID;
        Load = load ?? new List<ResourceSlot>();
        UnLoad = unload ?? new List<ResourceSlot>();
        MapColor = mapColor;
        BuildingTime = buildingTime;
        Cost = cost;
    }

    /// <summary>Footprint (ve?kos? + anchor) tejto továrne – jediný zdroj
    /// pravdy zostáva v IndicatrixAPI.</summary>
    public IndicatrixAPI.FactoryFootprint GetFootprint()
    {
        return IndicatrixAPI.GetFactoryFootprint(Mode);
    }

    /// <summary>True, ak továreň nejakú surovinu prijíma.</summary>
    public bool AcceptsResources => Load.Count > 0;

    /// <summary>True, ak továreň nejakú surovinu vydáva.</summary>
    public bool ProducesResources => UnLoad.Count > 0;
}

// ------------------------------------------------------------------
// DATABÁZA FIXNÝCH TOVÁRNÍ
// ------------------------------------------------------------------

/// <summary>
/// FactoryDatabase
/// ------------------------------------------------------------------
/// Statická databáza definícií. Drží fixné FactoryDefinition a sprístupní
/// ich cez GameManager.FactoryConstructionMode – presne ten istý vzor ako
/// IndicatrixAPI.GetFactoryFootprint(mode).
///
/// Každý slot je [type, amount, capacity] = [evidenčné číslo suroviny,
/// aktuálne množstvo, maximálna kapacita]. amount je počiatočná hodnota pri
/// položení a počas hry sa mení (FactoryInstance má vlastnú kópiu slotov).
///
/// FIXNÉ HODNOTY (podľa zadania):
///
///   Factory (tileID 4) – ťažba:
///     1.  Coal Mine     – Load: 0   | UnLoad: [1,0,1000]   (uhlie 0/1000)
///     2.  Forest        – Load: 0   | UnLoad: [2,0,1000]   (drevo 0/1000)
///     3.  Iron Ore Mine – Load: 0   | UnLoad: [3,0,1000]   (žel. ruda 0/1000)
///     4.  Gold Mine     – Load: 0   | UnLoad: [4,0,1000]   (zlatá ruda 0/1000)
///     5.  Silver Mine   – Load: 0   | UnLoad: [5,0,1000]   (strieb. ruda 0/1000)
///     6.  Farm          – Load: 0   | UnLoad: [6,0,3000] (dobytok 0/3000),
///                                              [7,0,1000] (pšenica 0/1000)
///     7.  Oil Wells     – Load: 0   | UnLoad: [8,0,10000]  (ropa 0/10000)
///
///   Processing (tileID 5) – spracovanie:
///     8.  Power Station       – Load: [1,0,5000] (uhlie 0/5000) | UnLoad: 0
///     9.  Sawmill             – Load: [2,0,3000] (drevo 0/3000) |
///                               UnLoad:[9,0,1000] (dosky 0/1000)
///     10. Oil Refinery        – Load: [8,0,8000] (ropa 0/8000)  |
///                               UnLoad:[10,0,2500] (plast 0/2500)
///     11. Electronics Factory – Load: [10,0,1000] (plast 0/1000),
///                                      [14,0,1000] (sklo 0/1000),
///                                      [13,0,1000] (kovy 0/1000) |
///                               UnLoad:[16,0,2500] (elektronika 0/2500)
///     12. Furniture Factory   – Load: [10,0,1000] (plast 0/1000),
///                                      [14,0,1000] (sklo 0/1000),
///                                      [13,0,1000] (kovy 0/1000),
///                                      [9,0,1000]  (dosky 0/1000) |
///                               UnLoad:[15,0,5000] (nábytok 0/5000)
///     13. Slaughterhouse      – Load: [6,0,750] (dobytok 0/750) |
///                               UnLoad:[11,0,1000] (mäso 0/1000)
///     14. Grain Factory       – Load: [7,0,1500] (pšenica 0/1500) |
///                               UnLoad:[12,0,2000] (múka 0/2000)
///     15. Smelter             – Load: [3,0,3000] (žel. ruda 0/3000) |
///                               UnLoad:[13,0,5000] (kovy 0/5000)
///     16. Glass Factory       – Load: [13,0,3000] (kovy 0/3000) |
///                               UnLoad:[14,0,1000] (sklo 0/1000)
///
/// Pridanie ďalšej továrne = pridať FactoryConstructionMode do GameManager,
/// footprint do IndicatrixAPI.GetFactoryFootprint a jeden záznam sem.
/// </summary>
public static class FactoryDatabase
{
    private static readonly Dictionary<GameManager.FactoryConstructionMode, FactoryDefinition> definitions
        = new Dictionary<GameManager.FactoryConstructionMode, FactoryDefinition>
    {
        // =====================================================================
        // FACTORY (tileID 4) – ťažba surovín (Load: 0)
        // =====================================================================

        // 1. Coal Mine ---------------------------------------------------------
        // Nič neprijíma, vydáva uhlie 0/10000.
        {
            GameManager.FactoryConstructionMode.CoalMine,
            new FactoryDefinition(
                id:     0,
                name:   "Coal Mine",
                mode:   GameManager.FactoryConstructionMode.CoalMine,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Coal, 0, 10000)          // UnLoad: [1,0,10000]
                },
                mapColor: new Color(0.15f, 0.15f, 0.15f),
                buildingTime: 12f,
                cost:         5000)
        },

        // 2. Forest ------------------------------------------------------------
        // Nič neprijíma, vydáva drevo 0/10000.
        {
            GameManager.FactoryConstructionMode.Forest,
            new FactoryDefinition(
                id:     1,
                name:   "Forest",
                mode:   GameManager.FactoryConstructionMode.Forest,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Wood, 0, 10000)          // UnLoad: [2,0,10000]
                },
                mapColor: new Color(0.13f, 0.55f, 0.13f),
                buildingTime: 10f,
                cost:         3500)
        },

        // 3. Iron Ore Mine -----------------------------------------------------
        // Nič neprijíma, vydáva železnú rudu 0/10000.
        {
            GameManager.FactoryConstructionMode.IronOreMine,
            new FactoryDefinition(
                id:     2,
                name:   "Iron Ore Mine",
                mode:   GameManager.FactoryConstructionMode.IronOreMine,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.IronOre, 0, 10000)       // UnLoad: [3,0,10000]
                },
                mapColor: new Color(0.55f, 0.27f, 0.07f),
                buildingTime: 14f,
                cost:         5500)
        },

        // 4. Gold Mine ---------------------------------------------------------
        // Nič neprijíma, vydáva zlatú rudu 0/10000.
        {
            GameManager.FactoryConstructionMode.GoldMine,
            new FactoryDefinition(
                id:     3,
                name:   "Gold Mine",
                mode:   GameManager.FactoryConstructionMode.GoldMine,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Gold, 0, 10000)          // UnLoad: [4,0,10000]
                },
                mapColor: new Color(1.00f, 0.84f, 0.00f),
                buildingTime: 18f,
                cost:         9000)
        },

        // 5. Silver Mine -------------------------------------------------------
        // Nič neprijíma, vydáva striebornú rudu 0/10000.
        {
            GameManager.FactoryConstructionMode.SilverMine,
            new FactoryDefinition(
                id:     4,
                name:   "Silver Mine",
                mode:   GameManager.FactoryConstructionMode.SilverMine,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Silver, 0, 10000)        // UnLoad: [5,0,10000]
                },
                mapColor: new Color(0.75f, 0.75f, 0.80f),
                buildingTime: 16f,
                cost:         8000)
        },

        // 6. Farm --------------------------------------------------------------
        // Nič neprijíma, vydáva dobytok 0/9000 a pšenicu 0/12000.
        {
            GameManager.FactoryConstructionMode.Farm,
            new FactoryDefinition(
                id:     5,
                name:   "Farm",
                mode:   GameManager.FactoryConstructionMode.Farm,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Livestock, 0, 9000),    // UnLoad: [6,0,9000]
                    new ResourceSlot(ResourceType.Grain,     0, 12000)     // UnLoad: [7,0,12000]
                },
                mapColor: new Color(0.80f, 0.75f, 0.30f),
                buildingTime: 10f,
                cost:         4000)
        },

        // 7. Oil Wells ---------------------------------------------------------
        // Nič neprijíma, vydáva ropu 0/15000.
        {
            GameManager.FactoryConstructionMode.OilWells,
            new FactoryDefinition(
                id:     6,
                name:   "Oil Wells",
                mode:   GameManager.FactoryConstructionMode.OilWells,
                tileID: 4,
                load:   new List<ResourceSlot>(),                         // Load: 0
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Oil, 0, 15000)          // UnLoad: [8,0,15000]
                },
                mapColor: new Color(0.20f, 0.10f, 0.25f),
                buildingTime: 20f,
                cost:         12000)
        },

        // =====================================================================
        // PROCESSING (tileID 5) – spracovanie surovín
        // =====================================================================

        // 8. Power Station -----------------------------------------------------
        // Prijíma uhlie 0/17000, nič nevydáva.
        {
            GameManager.FactoryConstructionMode.PowerStation,
            new FactoryDefinition(
                id:     7,
                name:   "Power Station",
                mode:   GameManager.FactoryConstructionMode.PowerStation,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Coal, 0, 17000)          // Load: [1,0,17000]
                },
                unload: new List<ResourceSlot>(),                         // UnLoad: 0
                mapColor: new Color(0.90f, 0.15f, 0.15f),
                buildingTime: 25f,
                cost:         8000)
        },

        // 9. Sawmill -----------------------------------------------------------
        // Prijíma drevo 0/7500, vydáva dosky 0/8500.
        {
            GameManager.FactoryConstructionMode.SawMill,
            new FactoryDefinition(
                id:     8,
                name:   "Sawmill",
                mode:   GameManager.FactoryConstructionMode.SawMill,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Wood, 0, 7500)          // Load: [2,0,7500]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Boards, 0, 8500)        // UnLoad: [9,0,8500]
                },
                mapColor: new Color(0.70f, 0.50f, 0.25f),
                buildingTime: 15f,
                cost:         4500)
        },

        // 10. Oil Refinery -----------------------------------------------------
        // Prijíma ropu 0/12000, vydáva plast 0/7500.
        {
            GameManager.FactoryConstructionMode.OilRefinery,
            new FactoryDefinition(
                id:     9,
                name:   "Oil Refinery",
                mode:   GameManager.FactoryConstructionMode.OilRefinery,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Oil, 0, 12000)           // Load: [8,0,12000]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Plastic, 0, 7500)       // UnLoad: [10,0,7500]
                },
                mapColor: new Color(0.90f, 0.20f, 0.70f),
                buildingTime: 28f,
                cost:         11000)
        },

        // 11. Electronics Factory ----------------------------------------------
        // Prijíma plast/sklo/kovy (každé 0/9500), vydáva elektroniku 0/9500.
        {
            GameManager.FactoryConstructionMode.ElectronicsFactory,
            new FactoryDefinition(
                id:     10,
                name:   "Electronics Factory",
                mode:   GameManager.FactoryConstructionMode.ElectronicsFactory,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Plastic, 0, 9500),      // Load: [10,0,9500]
                    new ResourceSlot(ResourceType.Glass,   0, 9500),      // Load: [14,0,9500]
                    new ResourceSlot(ResourceType.Metals,  0, 9500)       // Load: [13,0,9500]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.ElectronicsProducts, 0, 12500) // UnLoad: [16,0,12500]
                },
                mapColor: new Color(0.10f, 0.75f, 0.85f),
                buildingTime: 35f,
                cost:         15000)
        },

        // 12. Furniture Factory ------------------------------------------------
        // Prijíma plast/sklo/kovy/dosky (každé 0/1000), vydáva nábytok 0/18000.
        {
            GameManager.FactoryConstructionMode.FurnitureFactory,
            new FactoryDefinition(
                id:     11,
                name:   "Furniture Factory",
                mode:   GameManager.FactoryConstructionMode.FurnitureFactory,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Plastic, 0, 12000),      // Load: [10,0,12000]
                    new ResourceSlot(ResourceType.Glass,   0, 12000),      // Load: [14,0,12000]
                    new ResourceSlot(ResourceType.Metals,  0, 12000),      // Load: [13,0,12000]
                    new ResourceSlot(ResourceType.Boards,  0, 12000)       // Load: [9,0,12000]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Furniture, 0, 18000)     // UnLoad: [15,0,18000]
                },
                mapColor: new Color(0.60f, 0.35f, 0.75f),
                buildingTime: 32f,
                cost:         13000)
        },

        // 13. Slaughterhouse ---------------------------------------------------
        // Prijíma dobytok 0/7750, vydáva mäso 0/11000.
        {
            GameManager.FactoryConstructionMode.Slaughterhouse,
            new FactoryDefinition(
                id:     12,
                name:   "Slaughterhouse",
                mode:   GameManager.FactoryConstructionMode.Slaughterhouse,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Livestock, 0, 7750)      // Load: [6,0,7750]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Meat, 0, 11000)          // UnLoad: [11,0,11000]
                },
                mapColor: new Color(0.80f, 0.30f, 0.40f),
                buildingTime: 18f,
                cost:         5000)
        },

        // 14. Grain Factory ----------------------------------------------------
        // Prijíma pšenicu 0/7500, vydáva múku 0/9000.
        {
            GameManager.FactoryConstructionMode.GrainFactory,
            new FactoryDefinition(
                id:     13,
                name:   "Grain Factory",
                mode:   GameManager.FactoryConstructionMode.GrainFactory,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Grain, 0, 7500)         // Load: [7,0,7500]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Flour, 0, 9000)         // UnLoad: [12,0,9000]
                },
                mapColor: new Color(0.95f, 0.90f, 0.70f),
                buildingTime: 16f,
                cost:         6000)
        },

        // 15. Smelter ----------------------------------------------------------
        // Prijíma železnú rudu 0/9000, vydáva kovy 0/12000.
        {
            GameManager.FactoryConstructionMode.Smelter,
            new FactoryDefinition(
                id:     14,
                name:   "Smelter",
                mode:   GameManager.FactoryConstructionMode.Smelter,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.IronOre, 0, 9000)       // Load: [3,0,9000]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Metals, 0, 12000)        // UnLoad: [13,0,12000]
                },
                mapColor: new Color(1.00f, 0.45f, 0.00f),
                buildingTime: 26f,
                cost:         9500)
        },

        // 16. Glass Factory ----------------------------------------------------
        // Prijíma kovy 0/13000, vydáva sklo 0/12000.
        {
            GameManager.FactoryConstructionMode.GlassFactory,
            new FactoryDefinition(
                id:     15,
                name:   "Glass Factory",
                mode:   GameManager.FactoryConstructionMode.GlassFactory,
                tileID: 5,
                load:   new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Metals, 0, 13000)        // Load: [13,0,13000]
                },
                unload: new List<ResourceSlot>
                {
                    new ResourceSlot(ResourceType.Glass, 0, 12000)         // UnLoad: [14,0,12000]
                },
                mapColor: new Color(0.55f, 0.80f, 0.90f),
                buildingTime: 22f,
                cost:         7000)
        },
    };

    /// <summary>
    /// Vráti definíciu (šablónu) pre daný typ továrne, alebo null pre
    /// FactoryConstructionMode.None / neznámy typ.
    /// </summary>
    public static FactoryDefinition GetDefinition(GameManager.FactoryConstructionMode mode)
    {
        return definitions.TryGetValue(mode, out var def) ? def : null;
    }

    /// <summary>Všetky definície (napr. pre naplnenie UI alebo encyklopédiu).</summary>
    public static IEnumerable<FactoryDefinition> AllDefinitions => definitions.Values;
}

// ------------------------------------------------------------------
// KONKRÉTNA POLOŽENÁ TOVÁREN
// ------------------------------------------------------------------

/// <summary>
/// FactoryInstance
/// ------------------------------------------------------------------
/// Konkrétna továreň POLOŽENÁ na mape. Vzniká z FactoryDefinition pri
/// úspešnom IndicatrixAPI.SetTile(...) (pozri integráciu nižšie v komentári).
///
/// ROZDIEL oproti FactoryDefinition:
///   - Definition = nemenná šablóna, zdie?aná všetkými inštanciami toho typu.
///   - Instance   = vlastná KÓPIA Load/UnLoad slotov (množstvá sa počas hry
///                  menia – vlaky nakladajú/vykladajú) + pozícia footprintu
///                  na tile mape.
///
/// Týmto môžu na mape stá? napr. 3 Coal Mine, každá s iným zostatkom uhlia.
/// </summary>
public class FactoryInstance
{
    /// <summary>Odkaz na nemennú šablónu (Name, Mode, TileID, footprint).</summary>
    public FactoryDefinition Definition { get; private set; }

    /// <summary>Vlastná, menite?ná kópia príjmových slotov.</summary>
    public List<ResourceSlot> Load { get; private set; }

    /// <summary>Vlastná, menite?ná kópia výdajových slotov.</summary>
    public List<ResourceSlot> UnLoad { get; private set; }

    // Pozícia footprintu na tile mape
    // Lavý-dolný roh a rozmery (už po aplikovaní rotácie), ako ich vypočíta
    // IndicatrixAPI.SetTile. Slúži na spätné mapovanie tile ? továreň.

    public int OriginX { get; private set; }
    public int OriginZ { get; private set; }
    public int Width { get; private set; }
    public int Depth { get; private set; }
    public IndicatrixAPI.FactoryRotation Rotation { get; private set; }

    // =====================================================================
    // DOPLNKOVÉ POLOŽKY TOVÁRNE (ID + ekonomické / stavové príznaky)
    // =====================================================================

    /// <summary>
    /// FIXNÉ číselné ID typu továrne, prevzaté z FactoryDefinition.ID
    /// (1. Coal Mine = 0, 2. Forest = 1, … 16. Glass Factory = 15). Nastaví
    /// sa raz v konštruktore a počas hry sa nemení.
    /// </summary>
    public int ID { get; private set; }

    /// <summary>
    /// Mzda zamestnanca v tejto továrni. DEFAULT pri položení = 0.
    /// </summary>
    public int EmployeeSalary;

    /// <summary>
    /// Mzdový level (short int). DEFAULT pri položení = 0.
    /// </summary>
    public short LevelSalary;

    /// <summary>
    /// Príznak obsadenosti továrne. DEFAULT pri položení = false.
    /// </summary>
    public bool OccupancyFlag;

    public FactoryInstance(
        FactoryDefinition definition,
        int originX, int originZ,
        int width, int depth,
        IndicatrixAPI.FactoryRotation rotation)
    {
        Definition = definition;
        OriginX = originX;
        OriginZ = originZ;
        Width = width;
        Depth = depth;
        Rotation = rotation;

        // FIXNÉ ID typu prevezmeme z definície (pridelené v poradí databázy).
        ID = definition != null ? definition.ID : -1;

        // Hlboká kópia slotov – inštancia NESMIE meniť šablónu.
        Load = CloneSlots(definition.Load);
        UnLoad = CloneSlots(definition.UnLoad);

        // DEFAULTNÉ hodnoty pri položení továrne na tile map:
        // EmployeeSalary = 0, LevelSalary = 0, OccupancyFlag = false.
        // Týmto je default zaručený pre KAŽDÚ položenú továreň bez ohľadu na
        // to, ktorou cestou inštancia vznikne.
        ApplyPlacementDefaults();

        // Po položení začína fáza výstavby (ak má definícia kladný BuildingTime).
        InitConstruction();
    }

    /// <summary>
    /// Nastaví počiatočné (default) hodnoty továrne platné v okamihu položenia
    /// na tile map: EmployeeSalary = 0, LevelSalary = 0, OccupancyFlag = false.
    ///
    /// Jediné miesto, kde sú tieto defaulty definované. Volá ho konštruktor
    /// (záruka pre každú inštanciu) a explicitne aj GameManager priamo na mieste
    /// položenia (po FactoryRegistry.Register), aby bola požiadavka "vždy pri
    /// položení sa nastavia tieto položky" viditeľne splnená aj v ovládacom kóde.
    /// Operácia je idempotentná, takže dvojité zavolanie nič nepokazí.
    /// </summary>
    public void ApplyPlacementDefaults()
    {
        EmployeeSalary = 0;
        LevelSalary = 0;
        OccupancyFlag = false;
    }

    private static List<ResourceSlot> CloneSlots(List<ResourceSlot> source)
    {
        var copy = new List<ResourceSlot>(source.Count);
        foreach (var s in source)
            copy.Add(s.Clone());
        return copy;
    }

    // =====================================================================
    // STAV VÝSTAVBY (construction progress)
    // =====================================================================
    // Po položení továreň prejde fázou výstavby trvajúcou Definition.BuildingTime
    // sekúnd. Počas nej je IsUnderConstruction == true a továreň je zablokovaná
    // pre zmenu množstiev/kapacít (CanEditResources == false) aj pre demoláciu
    // (CanDemolish == false). Progres poháňa FactoryConstructionManager, ktorý
    // každý snímok volá AdvanceConstruction(Time.deltaTime).

    /// <summary>Koľko sekúnd výstavby už ubehlo (0 .. BuildTime).</summary>
    public float BuildElapsed { get; private set; }

    /// <summary>True, kým továreň nedosiahne 100 % výstavby.</summary>
    public bool IsUnderConstruction { get; private set; }

    /// <summary>True, ak je výstavba dokončená (opak IsUnderConstruction).</summary>
    public bool IsComplete => !IsUnderConstruction;

    /// <summary>Celkový čas výstavby v sekundách (z definície).</summary>
    public float BuildTime => Definition != null ? Definition.BuildingTime : 0f;

    /// <summary>Progres výstavby v rozsahu 0..1.</summary>
    public float BuildProgress01 =>
        BuildTime <= 0f ? 1f : Mathf.Clamp01(BuildElapsed / BuildTime);

    /// <summary>Progres výstavby v celých percentách 0..100 (na label).</summary>
    public int BuildPercent => Mathf.Clamp(Mathf.FloorToInt(BuildProgress01 * 100f), 0, 100);

    /// <summary>Zmena množstiev/kapacít je povolená až po dokončení výstavby.</summary>
    public bool CanEditResources => IsComplete;

    /// <summary>Demolácia továrne je povolená až po dokončení výstavby.</summary>
    public bool CanDemolish => IsComplete;

    /// <summary>
    /// Inicializuje stav výstavby. Volá konštruktor: ak má definícia kladný
    /// BuildingTime, továreň začína "vo výstavbe"; inak je hotová okamžite.
    /// </summary>
    private void InitConstruction()
    {
        BuildElapsed = 0f;
        IsUnderConstruction = (Definition != null && Definition.BuildingTime > 0f);
    }

    /// <summary>
    /// Posunie výstavbu o <paramref name="deltaSeconds"/>. Po dosiahnutí
    /// celkového času sa továreň označí ako dokončená (IsUnderConstruction =
    /// false) a sprístupní sa pre zmeny aj demoláciu.
    /// </summary>
    public void AdvanceConstruction(float deltaSeconds)
    {
        if (!IsUnderConstruction) return;

        BuildElapsed += deltaSeconds;
        if (BuildElapsed >= BuildTime)
        {
            BuildElapsed = BuildTime;
            IsUnderConstruction = false;
        }
    }

    /// <summary>Okamžite dokončí výstavbu (napr. cheat / load uloženej hry).</summary>
    public void ForceCompleteConstruction()
    {
        BuildElapsed = BuildTime;
        IsUnderConstruction = false;
    }

    /// <summary>
    /// Bezpečné nastavenie množstva v slote – funguje LEN po dokončení
    /// výstavby (počas výstavby je zmena zablokovaná a vráti false).
    /// </summary>
    public bool TrySetSlotAmount(ResourceSlot slot, int newAmount)
    {
        if (!CanEditResources || slot == null) return false;
        slot.amount = Mathf.Clamp(newAmount, 0, slot.capacity);
        return true;
    }

    /// <summary>
    /// Bezpečné nastavenie kapacity slotu – funguje LEN po dokončení výstavby.
    /// Ak nová kapacita klesne pod aktuálne množstvo, množstvo sa oreže.
    /// </summary>
    public bool TrySetSlotCapacity(ResourceSlot slot, int newCapacity)
    {
        if (!CanEditResources || slot == null) return false;
        slot.capacity = Mathf.Max(0, newCapacity);
        if (slot.amount > slot.capacity) slot.amount = slot.capacity;
        return true;
    }

    // Pomocné prístupy k slotom

    /// <summary>Názov továrne (skratka cez Definition).</summary>
    public string Name => Definition.Name;

    /// <summary>Prvý príjmový slot pre danú surovinu, alebo null.</summary>
    public ResourceSlot GetLoadSlot(ResourceType type)
    {
        foreach (var s in Load)
            if (s.type == type) return s;
        return null;
    }

    /// <summary>Prvý výdajový slot pre danú surovinu, alebo null.</summary>
    public ResourceSlot GetUnLoadSlot(ResourceType type)
    {
        foreach (var s in UnLoad)
            if (s.type == type) return s;
        return null;
    }

    /// <summary>True, ak tile [x,z] patrí do footprintu tejto továrne.</summary>
    public bool ContainsTile(int x, int z)
    {
        return x >= OriginX && x < OriginX + Width
            && z >= OriginZ && z < OriginZ + Depth;
    }

    public override string ToString()
    {
        return $"FactoryInstance '{Name}' @[{OriginX},{OriginZ}] {Width}x{Depth} (rot {Rotation})";
    }
}

// ------------------------------------------------------------------
// REGISTER POLOŽENÝCH TOVÁRNÍ
// ------------------------------------------------------------------

/// <summary>
/// FactoryRegistry
/// ------------------------------------------------------------------
/// Centrálna evidencia VŠETKÝCH položených FactoryInstance v hre. Tile mapa
/// (IndicatrixAPI) naňalej drží len textúru/tileID/stateID – nevie nič o
/// kapacitách ani o tom, "ktoré tily tvoria jednu továreň". Tento register
/// dopĺňa práve toto: jeden zoznam inštancií + rýchle spätné mapovanie
/// tile [x,z] ? FactoryInstance.
///
/// PREČO STATIC:
///   Register je herne jedinečný (jedna mapa = jedna sada tovární), rovnako
///   ako FactoryDatabase. Static prístup je konzistentný a netreba riešiť
///   serializáciu MonoBehaviour referencie. Ak by si v budúcnosti chcel mať
///   register ako MonoBehaviour singleton (kvôli Inspectoru), stačí obaliť
///   tieto metódy do inštančnej triedy – API zostane rovnaké.
///
/// SPÄTNÉ MAPOVANIE tile ? továreň:
///   Dictionary<long, FactoryInstance> kde kľúč = TileKey(x,z). Pri položení
///   sa zaregistrujú VŠETKY tily footprintu, takže klik na ľubovoľný tile
///   továrne (napr. pre info okno alebo demolish) nájde inštanciu v O(1).
/// </summary>
public static class FactoryRegistry
{
    /// <summary>Všetky položené továrne (poradie = poradie položenia).</summary>
    private static readonly List<FactoryInstance> instances = new List<FactoryInstance>();

    /// <summary>Spätné mapovanie: každý tile footprintu ? jeho FactoryInstance.</summary>
    private static readonly Dictionary<long, FactoryInstance> tileToFactory
        = new Dictionary<long, FactoryInstance>();

    /// <summary>Stabilný kľúč pre dvojicu tile súradníc (x, z).</summary>
    private static long TileKey(int x, int z) => ((long)x << 32) | (uint)z;

    /// <summary>Všetky aktuálne položené továrne (len na čítanie).</summary>
    public static IReadOnlyList<FactoryInstance> All => instances;

    /// <summary>Počet položených tovární.</summary>
    public static int Count => instances.Count;

    /// <summary>
    /// Zaregistruje novú položenú továreň. Volá sa po úspešnom
    /// IndicatrixAPI.SetTile(...) (placed == true). Origin/width/depth musia
    /// zodpovedať footprintu zapísanému do tile mapy – preto sa berú priamo
    /// z out parametrov SetTile.
    ///
    /// Vráti vytvorenú FactoryInstance (alebo null, ak je definícia neznáma).
    /// </summary>
    public static FactoryInstance Register(
        FactoryDefinition definition,
        int originX, int originZ,
        int width, int depth,
        IndicatrixAPI.FactoryRotation rotation)
    {
        if (definition == null)
        {
            Debug.LogWarning("[FactoryRegistry] Register: definition == null – preskočené.");
            return null;
        }

        var inst = new FactoryInstance(definition, originX, originZ, width, depth, rotation);
        instances.Add(inst);

        // Zaregistruj každý tile footprintu do spätného mapovania.
        for (int x = originX; x < originX + width; x++)
            for (int z = originZ; z < originZ + depth; z++)
                tileToFactory[TileKey(x, z)] = inst;

        Debug.Log($"[FactoryRegistry] Zaregistrovaná {inst}. Spolu tovární: {instances.Count}.");
        return inst;
    }

    /// <summary>
    /// Vráti továreň, ktorej footprint obsahuje tile [x,z], alebo null ak
    /// na danom tile žiadna továreň nie je. O(1) cez spätné mapovanie.
    /// </summary>
    public static FactoryInstance GetFactoryAt(int x, int z)
    {
        return tileToFactory.TryGetValue(TileKey(x, z), out var inst) ? inst : null;
    }

    /// <summary>
    /// Odregistruje továreň (napr. pri demolish). Odstráni inštanciu zo
    /// zoznamu aj všetky jej tily zo spätného mapovania.
    ///
    /// POZN.: Samotné vymazanie tilov z tile mapy (IndicatrixAPI) je
    /// samostatná operácia – tento register len prestane továreň evidovať.
    ///
    /// Vráti true, ak sa továreň našla a odstránila.
    /// </summary>
    public static bool Unregister(FactoryInstance inst)
    {
        if (inst == null || !instances.Remove(inst))
            return false;

        for (int x = inst.OriginX; x < inst.OriginX + inst.Width; x++)
            for (int z = inst.OriginZ; z < inst.OriginZ + inst.Depth; z++)
            {
                long key = TileKey(x, z);
                // Odstráň len ak kľúč naozaj patrí tejto inštancii
                // (ochrana pri prípadnom prekryve – nemalo by nastať).
                if (tileToFactory.TryGetValue(key, out var mapped) && mapped == inst)
                    tileToFactory.Remove(key);
            }

        Debug.Log($"[FactoryRegistry] Odregistrovaná {inst}. Spolu tovární: {instances.Count}.");
        return true;
    }

    /// <summary>Odregistruje továreň stojacu na tile [x,z], ak nejaká je.</summary>
    public static bool UnregisterAt(int x, int z)
    {
        return Unregister(GetFactoryAt(x, z));
    }

    /// <summary>Vymaže celý register (napr. pri naňítaní novej mapy).</summary>
    public static void Clear()
    {
        instances.Clear();
        tileToFactory.Clear();
        Debug.Log("[FactoryRegistry] Register vyčistený.");
    }
}

GameManager.cs

using System;
using System.Collections.Generic;
using System.IO;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.HID;
using UnityEngine.Splines;


public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    // =====================================================================
    // REŽIMY KONŠTRUKCIE (pôvodné)
    // =====================================================================

    public enum RailConstructionMode
    {
        None,
        LevelUp,
        LevelDown,
        Demolish,
        RailHorizontal,
        RailVertical,
        RailCrossroad,
        RailCurveRightBottom,
        RailCurveLeftBottom,
        RailCurveRightTop,
        RailCurveLeftTop,
        StationHorizontal,
        StationVertical,
        DepotHorizontalBottom,
        DepotVerticalBottom,
        DepotHorizontalTop,
        DepotVerticalTop,
        RailSwitchHorizontalBottom,
        RailSwitchHorizontalTop,
        RailSwitchVerticalBottom,
        RailSwitchVerticalTop
    }

    public RailConstructionMode CurrentRailConstructionMode { get; private set; }

    public void SetTerrainMode(RailConstructionMode RCmode)
    {
        CurrentRailConstructionMode = RCmode;
        // Pri prepnutí do iného režimu ukončíme prípadné čakanie na vstup vlaku
        if (RCmode != RailConstructionMode.None)
        {
            // Aktivácia RAIL režimu vyzbrojí aj ROAD a FACTORY režim (mutex)
            // a tiež režim prideľovania personálu (Staff→Factory).
            IsStaffToFactoryMode = false;
            pendingStaff = null;
            CurrentRoadConstructionMode = RoadConstructionMode.None;
            CurrentFactoryConstructionMode = FactoryConstructionMode.None;
            trainInputMode = TrainInputMode.None;
            pendingDepotTile = null;
            collectingStations = false;
            pendingRoadDepotTile = null;
            collectingRoadStations = false;
            IndAPI?.HideAllSnapVisuals();
        }
    }

    // =====================================================================
    // ROAD CONSTRUCTION MODE (analógia k RailConstructionMode)
    //
    // Cesty a železnice sú dva oddelené systémy – obe používajú rovnaký
    // tile grid v IndicatrixAPI, ale s rôznou TileCategory (Rail / Road),
    // čo umožňuje aby SetTile pre ROAD nezasiahol existujúci RAIL graf
    // a naopak. V jednom momente môže byť aktívny BUĎ rail BUĎ road režim
    // (mutex), takže OnMovement / OnClick prebehne čistú vetvu podľa toho,
    // ktorý režim je nastavený.
    // =====================================================================

    public enum RoadConstructionMode
    {
        None,
        LevelUp,
        LevelDown,
        Demolish,
        RoadHorizontal,
        RoadVertical,
        RoadCrossroad,
        RoadCurveRightBottom,
        RoadCurveLeftBottom,
        RoadCurveRightTop,
        RoadCurveLeftTop,
        StationHorizontal,
        StationVertical,
        DepotHorizontalBottom,
        DepotVerticalBottom,
        DepotHorizontalTop,
        DepotVerticalTop,
        RoadSwitchHorizontalBottom,
        RoadSwitchHorizontalTop,
        RoadSwitchVerticalBottom,
        RoadSwitchVerticalTop
    }

    public RoadConstructionMode CurrentRoadConstructionMode { get; private set; }

    /// <summary>
    /// Preťaženie SetTerrainMode pre ROAD systém. Analogicky k RAIL verzii.
    /// Mutex: aktivácia ROAD režimu zruší RAIL režim (a opačne).
    /// </summary>
    public void SetTerrainMode(RoadConstructionMode RCmode)
    {
        CurrentRoadConstructionMode = RCmode;
        if (RCmode != RoadConstructionMode.None)
        {
            // Aktivácia ROAD režimu zruší RAIL režim aj vlakový vstup
            // a tiež režim prideľovania personálu (Staff→Factory).
            IsStaffToFactoryMode = false;
            pendingStaff = null;
            CurrentRailConstructionMode = RailConstructionMode.None;
            CurrentFactoryConstructionMode = FactoryConstructionMode.None;
            trainInputMode = TrainInputMode.None;
            pendingDepotTile = null;
            collectingStations = false;
            pendingRoadDepotTile = null;
            collectingRoadStations = false;
            IndAPI?.HideAllSnapVisuals();
        }
    }

    // =====================================================================
    // FACTORY CONSTRUCTION MODE (analógia k RAIL/ROAD ConstructionMode)
    //
    // Továrne sú tretí, samostatný systém. Na rozdiel od RAIL/ROAD NIE SÚ
    // dopravným grafom – sú to staticky umiestnené viac-tile objekty
    // (footprint 2×3, 3×3, 2×2 ...). Zdieľajú ten istý tile grid v
    // IndicatrixAPI, ale s TileCategory.Factory, takže TrainSystem ani
    // VehicleSystem ich nevidia (GetTileByIndex filtruje na Rail/Road).
    //
    // tileID konvencia:
    //   4 = Factory     (CoalMine, Forest, IronOreMine, GoldMine, SilverMine,
    //                    Farm, OilWells)
    //   5 = Processing  (PowerStation, SawMill, OilRefinery, ElectronicsFactory,
    //                    FurnitureFactory, Slaughterhouse, GrainFactory, Smelter,
    //                    GlassFactory)
    // Konkrétny typ továrne rozlišuje stateID = (int)FactoryConstructionMode.
    //
    // V jednom momente je aktívny BUĎ rail, BUĎ road, BUĎ factory režim
    // (mutex) – rovnako ako medzi RAIL a ROAD.
    // =====================================================================

    public enum FactoryConstructionMode
    {
        None,

        // ── Factory (tileID 4) – ťažba surovín ──
        CoalMine,            // tileID 4 (Factory),    footprint 2×3
        Forest,              // tileID 4 (Factory),    footprint 3×3
        IronOreMine,         // tileID 4 (Factory),    footprint 3×3
        GoldMine,            // tileID 4 (Factory),    footprint 3×3
        SilverMine,          // tileID 4 (Factory),    footprint 3×3
        Farm,                // tileID 4 (Factory),    footprint 3×3
        OilWells,            // tileID 4 (Factory),    footprint 3×3

        // ── Processing (tileID 5) – spracovanie surovín ──
        PowerStation,        // tileID 5 (Processing), footprint 2×2
        SawMill,             // tileID 5 (Processing), footprint 2×2
        OilRefinery,         // tileID 5 (Processing), footprint 2×2
        ElectronicsFactory,  // tileID 5 (Processing), footprint 3×3
        FurnitureFactory,    // tileID 5 (Processing), footprint 3×3
        Slaughterhouse,      // tileID 5 (Processing), footprint 2×2
        GrainFactory,        // tileID 5 (Processing), footprint 2×3
        Smelter,             // tileID 5 (Processing), footprint 2×3
        GlassFactory         // tileID 5 (Processing), footprint 2×2
    }

    public FactoryConstructionMode CurrentFactoryConstructionMode { get; private set; }

    /// <summary>
    /// Aktuálna rotácia, s ktorou sa bude umiestňovať továreň.
    ///
    /// DEFAULT: FactoryRotation.Deg0 (bez otočenia – natívna orientácia
    /// definovaná v IndicatrixAPI.GetFactoryFootprint).
    ///
    /// Hodnotu mení hráč napr. klávesou (pozri RotateFactory nižšie) alebo
    /// UI tlačidlom. SetTile aj SnapAreaFace ju čítajú, takže náhľad
    /// footprintu aj výsledné umiestnenie zostávajú synchronizované.
    /// </summary>
    public IndicatrixAPI.FactoryRotation CurrentFactoryRotation { get; private set; }
        = IndicatrixAPI.FactoryRotation.Deg0;

    /// <summary>
    /// Posunie rotáciu továrne o 90° (cyklicky Deg0→Deg90→Deg180→Deg270→Deg0).
    /// Vhodné napojiť na klávesu (napr. R) alebo na UI tlačidlo "Rotate".
    /// </summary>
    public void RotateFactory()
    {
        int next = ((int)CurrentFactoryRotation + 1) % 4;
        CurrentFactoryRotation = (IndicatrixAPI.FactoryRotation)next;
        Debug.Log($"[GameManager] Rotácia továrne nastavená na {CurrentFactoryRotation}.");
    }

    /// <summary>
    /// Priame nastavenie rotácie továrne na konkrétnu hodnotu.
    /// </summary>
    public void SetFactoryRotation(IndicatrixAPI.FactoryRotation rotation)
    {
        CurrentFactoryRotation = rotation;
        Debug.Log($"[GameManager] Rotácia továrne nastavená na {CurrentFactoryRotation}.");
    }

    /// <summary>
    /// Preťaženie SetTerrainMode pre FACTORY systém. Analogicky k RAIL/ROAD.
    /// Mutex: aktivácia FACTORY režimu zruší RAIL aj ROAD režim.
    /// </summary>
    public void SetTerrainMode(FactoryConstructionMode FCmode)
    {
        CurrentFactoryConstructionMode = FCmode;
        if (FCmode != FactoryConstructionMode.None)
        {
            // Pri výbere novej továrne resetujeme rotáciu na Deg0, aby
            // nezostala "zaseknutá" z predchádzajúceho výberu. Hráč si ju
            // vie kedykoľvek znovu otočiť klávesou R.
            CurrentFactoryRotation = IndicatrixAPI.FactoryRotation.Deg0;

            // Aktivácia FACTORY režimu zruší RAIL aj ROAD režim aj vstupy
            // a tiež režim prideľovania personálu (Staff→Factory).
            IsStaffToFactoryMode = false;
            pendingStaff = null;
            CurrentRailConstructionMode = RailConstructionMode.None;
            CurrentRoadConstructionMode = RoadConstructionMode.None;
            trainInputMode = TrainInputMode.None;
            pendingDepotTile = null;
            collectingStations = false;
            pendingRoadDepotTile = null;
            collectingRoadStations = false;
            IndAPI?.HideAllSnapVisuals();
        }
    }

    // =====================================================================
    // STAFF → FACTORY ASSIGNMENT MODE (ConstructionModeStaffToFactory)
    //
    // Štvrtý "konštrukčný" režim – NIE je to ale stavba na tile mape, ale
    // PRIDELENIE personálu (StaffManagement) do už existujúcej továrne.
    // Spúšťa ho UI okno StaffManagementMenuUI po kliknutí na *HireButton
    // (napr. MinerHireButton) – odovzdá naplnenú StaffManagement štruktúru.
    //
    // SPRÁVANIE (analogické k FACTORY/RAIL/ROAD vetvám v Update):
    //   • OnMovement: IndAPI.SnapMeshFace(...) – zvýrazní 1×1 ŠTVOREC + FACE
    //     pod kurzorom (rovnaký vizuál ako pri kliku na hotovú továreň).
    //   • OnClick:    hráč klikne na ĽUBOVOĽNÚ továreň. Keďže
    //     FactoryRegistry.GetFactoryAt(...) mapuje KAŽDÝ tile footprintu na tú
    //     istú FactoryInstance, footprint (2×3 / 3×3 / 2×2) je zohľadnený
    //     automaticky – stačí kliknúť na hociktorý tile továrne.
    //
    // POROVNANIE pri kliku (StaffManagement vs FactoryInstance):
    //   • ak sa StaffManagement.ID == FactoryInstance.ID →
    //         továreň.LevelSalary    = staff.LevelSalary;
    //         továreň.EmployeeSalary = staff.EmployeeSalary;
    //         továreň.OccupancyFlag  = true;
    //   • inak →
    //         továreň.OccupancyFlag  = false;  (ostatné polia bez zmeny)
    //
    // Mutex: aktivácia tohto režimu zruší RAIL/ROAD/FACTORY režim aj vstupy
    // (a opačne – aktivácia ktoréhokoľvek konštrukčného režimu zruší tento).
    // =====================================================================

    /// <summary>True, kým je aktívny režim prideľovania personálu do továrne.</summary>
    public bool IsStaffToFactoryMode { get; private set; }

    /// <summary>
    /// Práve "nesená" dávka personálu, ktorú UI naplnilo pri kliku na
    /// *HireButton. Po kliknutí na továreň sa porovná/prenesie do nej.
    /// </summary>
    StaffManagement pendingStaff;

    /// <summary>
    /// Spustí režim prideľovania personálu do továrne. Volá ho StaffManagement
    /// UI (napr. SMMinerPanelUI) po naplnení StaffManagement a zatvorení okna.
    /// Mutex – zhodí všetky ostatné režimy (rovnako ako SetTerrainMode).
    /// </summary>
    public void EnterStaffToFactoryMode(StaffManagement staff)
    {
        if (staff == null)
        {
            Debug.LogWarning("[GameManager] EnterStaffToFactoryMode: staff == null – ignorované.");
            return;
        }

        pendingStaff = staff;
        IsStaffToFactoryMode = true;

        // Mutex – zruš RAIL/ROAD/FACTORY režim aj vlakový/cestný vstup.
        CurrentRailConstructionMode = RailConstructionMode.None;
        CurrentRoadConstructionMode = RoadConstructionMode.None;
        CurrentFactoryConstructionMode = FactoryConstructionMode.None;
        trainInputMode = TrainInputMode.None;
        pendingDepotTile = null;
        collectingStations = false;
        pendingRoadDepotTile = null;
        collectingRoadStations = false;
        IndAPI?.HideAllSnapVisuals();

        Debug.Log($"[GameManager] Staff→Factory režim AKTÍVNY. Klikni na továreň. {staff}");
    }

    /// <summary>
    /// Ukončí režim prideľovania personálu (po vykonaní akcie alebo cez ESC)
    /// a skryje snap vizuál.
    /// </summary>
    public void ExitStaffToFactoryMode()
    {
        IsStaffToFactoryMode = false;
        pendingStaff = null;
        IndAPI?.HideAllSnapVisuals();
    }

    // =====================================================================
    // STAV VSTUPU VLAKU
    //
    // POZNÁMKA: Klávesové skratky Q, W, S, P, R, T boli ODSTRÁNENÉ.
    // Funkcionalita bola presunutá do DepotRailConstructionMenuUI, ktoré sa
    // otvára kliknutím na Depot tile.
    //
    // Z pôvodných režimov ostal LEN AddStations (W) – pretože vyžaduje
    // viacero klikov na tile mapu (na stanice). Ostatné akcie sa volajú
    // priamo cez Request*ForDepot(...) metódy bez čakania na klik na tile.
    // =====================================================================

    enum TrainInputMode
    {
        None,
        AddStations,        // Čakáme na klik na RAIL Station(e) pre vopred vybrané RAIL depo
        AddStationsRoad     // Čakáme na klik na ROAD Station(e) pre vopred vybrané ROAD depo
    }

    TrainInputMode trainInputMode = TrainInputMode.None;

    // Pre režim AddStations – zapamätáme depot, potom čakáme na kliky na stanice
    Vector2Int? pendingDepotTile = null;
    bool collectingStations = false;

    // Pre režim AddStationsRoad – zapamätáme ROAD depot, potom čakáme na
    // kliky na ROAD stanice (autobus./nákl. zastávky).
    Vector2Int? pendingRoadDepotTile = null;
    bool collectingRoadStations = false;

    // Spätná väzba pre DepotRailConstructionMenuUI – aby vedelo, či práve
    // beží "Define Route" režim pre dané depo
    public bool IsCollectingStationsForDepot(int dx, int dz)
    {
        return trainInputMode == TrainInputMode.AddStations
               && collectingStations
               && pendingDepotTile.HasValue
               && pendingDepotTile.Value.x == dx
               && pendingDepotTile.Value.y == dz;
    }

    /// <summary>
    /// ROAD ekvivalent IsCollectingStationsForDepot – spätná väzba pre
    /// DepotRoadConstructionMenuUI.
    /// </summary>
    public bool IsCollectingStationsForRoadDepot(int dx, int dz)
    {
        return trainInputMode == TrainInputMode.AddStationsRoad
               && collectingRoadStations
               && pendingRoadDepotTile.HasValue
               && pendingRoadDepotTile.Value.x == dx
               && pendingRoadDepotTile.Value.y == dz;
    }

    Camera cam;

    // =====================================================================
    // NULL-SAFE LAZY PROPERTIES
    // Chránia pred NullReferenceException ak Awake/Start prebehli v zlom poradí
    // =====================================================================

    static TrainSystem TrainSys
    {
        get
        {
            if (TrainSystem.instance == null)
                TrainSystem.instance = UnityEngine.Object.FindFirstObjectByType<TrainSystem>();
            return TrainSystem.instance;
        }
    }

    /// <summary>
    /// Lazy referencia na VehicleSystem (cestný ekvivalent TrainSystem).
    /// VehicleSystem je úplne autonómny – obsluhuje len ROAD dlaždice
    /// (skrz GetTileByIndexAny v IndicatrixAPI s filtrom kategórie).
    /// </summary>
    static VehicleSystem VehicleSys
    {
        get
        {
            if (VehicleSystem.instance == null)
                VehicleSystem.instance = UnityEngine.Object.FindFirstObjectByType<VehicleSystem>();
            return VehicleSystem.instance;
        }
    }

    static IndicatrixAPI IndAPI
    {
        get
        {
            if (IndicatrixAPI.instance == null)
                IndicatrixAPI.instance = UnityEngine.Object.FindFirstObjectByType<IndicatrixAPI>();
            return IndicatrixAPI.instance;
        }
    }

    // Lazy referencia na DepotRailConstructionMenuUI (môže byť v scéne neaktívne)
    DepotRailConstructionMenuUI _depotRailMenuUI;
    DepotRailConstructionMenuUI DepotRailMenuUI
    {
        get
        {
            if (_depotRailMenuUI == null)
                _depotRailMenuUI = UnityEngine.Object.FindFirstObjectByType<DepotRailConstructionMenuUI>(FindObjectsInactive.Include);
            return _depotRailMenuUI;
        }
    }

    // Lazy referencia na DepotRoadConstructionMenuUI (môže byť v scéne neaktívne)
    DepotRoadConstructionMenuUI _depotRoadMenuUI;
    DepotRoadConstructionMenuUI DepotRoadMenuUI
    {
        get
        {
            if (_depotRoadMenuUI == null)
                _depotRoadMenuUI = UnityEngine.Object.FindFirstObjectByType<DepotRoadConstructionMenuUI>(FindObjectsInactive.Include);
            return _depotRoadMenuUI;
        }
    }

    // Lazy referencia na StatusFactoryMenuUI (môže byť v scéne neaktívne).
    // Okno je pri štarte skryté (SetActive(false) v jeho Start()), preto sa
    // hľadá s FindObjectsInactive.Include – rovnako ako Depot menu okná.
    StatusFactoryMenuUI _statusFactoryMenuUI;
    StatusFactoryMenuUI StatusFactoryMenuUI
    {
        get
        {
            if (_statusFactoryMenuUI == null)
                _statusFactoryMenuUI = UnityEngine.Object.FindFirstObjectByType<StatusFactoryMenuUI>(FindObjectsInactive.Include);
            return _statusFactoryMenuUI;
        }
    }

    // Lazy referencia na StatusStationMenuUI (môže byť v scéne neaktívne).
    // Okno je pri štarte skryté (SetActive(false) v jeho Start()), preto sa
    // hľadá s FindObjectsInactive.Include – rovnako ako StatusFactoryMenuUI
    // a Depot menu okná.
    StatusStationMenuUI _statusStationMenuUI;
    StatusStationMenuUI StatusStationMenuUI
    {
        get
        {
            if (_statusStationMenuUI == null)
                _statusStationMenuUI = UnityEngine.Object.FindFirstObjectByType<StatusStationMenuUI>(FindObjectsInactive.Include);
            return _statusStationMenuUI;
        }
    }

    // Lazy referencia na StatusErrorMenuUI (môže byť v scéne neaktívne).
    // Univerzálne chybové okno – pri štarte skryté (SetActive(false) v jeho
    // Start()), preto sa hľadá s FindObjectsInactive.Include, rovnako ako
    // ostatné Status / Depot menu okná.
    StatusErrorMenuUI _statusErrorMenuUI;
    StatusErrorMenuUI StatusErrorMenuUI
    {
        get
        {
            if (_statusErrorMenuUI == null)
                _statusErrorMenuUI = UnityEngine.Object.FindFirstObjectByType<StatusErrorMenuUI>(FindObjectsInactive.Include);
            return _statusErrorMenuUI;
        }
    }

    // =====================================================================
    // CHYBOVÉ HLÁSENIA (univerzálne okno StatusErrorMenuUI)
    //
    // Chyby sa VYVOLÁVAJÚ na mieste incidentu (tu v OnClick build vetvách,
    // v budúcnosti hocikde inde). Vždy sa otvorí TO ISTÉ okno, len s INÝM
    // textom – kanonické znenia sú v GameErrors.cs.
    // =====================================================================

    /// <summary>
    /// Zobrazí hráčovi chybovú hlášku v univerzálnom okne StatusErrorMenuUI.
    /// Jediné miesto, cez ktoré GameManager otvára chybové okno – volajúci
    /// dodá text (najlepšie konštantu z GameErrors).
    /// </summary>
    void ReportError(string message)
    {
        var errMenu = StatusErrorMenuUI;
        if (errMenu != null)
        {
            errMenu.OpenWithError(message);
        }
        else
        {
            // Okno nie je v scéne – aspoň nech sa chyba neutopí ticho.
            Debug.LogWarning($"[GameManager] StatusErrorMenuUI nie je v scéne. " +
                             $"Chyba: \"{message}\"");
        }
    }

    // =====================================================================
    // EKONOMIKA KONŠTRUKCIE – spoplatnenie stavby a refund za demoláciu
    //
    // Cenník je centralizovaný v ConstructionCosts.cs; tu sú len tenké
    // obaly, ktoré cenu odpočítajú/pripočítajú cez GameEconomy a v prípade
    // nedostatku kreditov stavbu zablokujú (ReportError + return false).
    //
    // Ak GameEconomy.instance nie je v scéne, spoplatnenie sa preskočí
    // (hra ostane funkčná aj bez peňažného systému).
    // =====================================================================

    /// <summary>
    /// Pokus o zaplatenie RAIL stavby. true = zaplatené (alebo zadarmo / bez
    /// ekonomiky), false = nedostatok kreditov → volajúci NESMIE stavať.
    /// </summary>
    bool TryChargeRailBuild(RailConstructionMode mode)
        => TryChargeBuild(ConstructionCosts.RailBuildCost(mode));

    /// <summary>
    /// Pokus o zaplatenie ROAD stavby. true = zaplatené, false = nedostatok.
    /// </summary>
    bool TryChargeRoadBuild(RoadConstructionMode mode)
        => TryChargeBuild(ConstructionCosts.RoadBuildCost(mode));

    /// <summary>
    /// Pokus o zaplatenie postavenia TOVÁRNE. true = zaplatené (alebo zadarmo /
    /// bez ekonomiky), false = nedostatok kreditov → továreň sa NESMIE položiť.
    /// Cena sa berie z ConstructionCosts.FactoryBuildCost (= FactoryDefinition.Cost).
    /// </summary>
    bool TryChargeFactoryBuild(FactoryConstructionMode mode)
    {
        uint cost = ConstructionCosts.FactoryBuildCost(mode);
        bool charged = TryChargeBuild(cost);

        // EVIDENCIA pre ročnú uzávierku: cena továrne sa odpočítava HNEĎ pri
        // položení (geometria je už overená vyššie, SetTile nezlyhá), takže
        // úspešné spoplatnenie = postavená továreň. Zaznamenáme len reálne
        // platený nákup (cost > 0).
        if (charged && cost > 0u)
            BudgetSystem.instance?.RecordFactoryPurchase(cost);

        return charged;
    }

    /// <summary>Spoločné jadro – odpočíta cenu, ak je dosť kreditov.</summary>
    bool TryChargeBuild(uint cost)
    {
        if (cost == 0u) return true;                    // úkon nič nestojí
        if (GameEconomy.instance == null) return true;  // ekonomika nie je v scéne

        if (!GameEconomy.instance.TrySpendCredits(cost))
        {
            ReportError($"Nedostatok kreditov – tento úkon stojí {cost} CR.");
            return false;
        }
        return true;
    }

    /// <summary>
    /// Pripočíta na konto refund za zdemolovaný tile (50 % zo základnej ceny).
    /// tileInfo treba prečítať PRED vymazaním dlaždice (drží stateID + category).
    /// </summary>
    void RefundDemolishedTile(IndicatrixAPI.TileData tileInfo)
    {
        if (GameEconomy.instance == null) return;

        uint refund = ConstructionCosts.DemolishRefund(tileInfo);
        if (refund > 0u)
            GameEconomy.instance.AddCredits(refund);
    }

    /// <summary>
    /// Vráti true, ak je daný RAIL režim PLACEMENT (kladie tile cez SetTile s
    /// tileID != 0 – koľaje, stanice, depá, výhybky). Pre None, LevelUp,
    /// LevelDown (úprava terénu, nevolá SetTile) a Demolish (SetTile = 0)
    /// vracia false – tieto sa proti obsadenosti NEkontrolujú.
    /// </summary>
    static bool IsRailPlacementMode(RailConstructionMode mode)
    {
        switch (mode)
        {
            case RailConstructionMode.None:
            case RailConstructionMode.LevelUp:
            case RailConstructionMode.LevelDown:
            case RailConstructionMode.Demolish:
                return false;
            default:
                return true;
        }
    }

    /// <summary>
    /// ROAD ekvivalent IsRailPlacementMode.
    /// </summary>
    static bool IsRoadPlacementMode(RoadConstructionMode mode)
    {
        switch (mode)
        {
            case RoadConstructionMode.None:
            case RoadConstructionMode.LevelUp:
            case RoadConstructionMode.LevelDown:
            case RoadConstructionMode.Demolish:
                return false;
            default:
                return true;
        }
    }

    // =====================================================================
    // OCHRANNÁ ZÓNA STANÍC (stanica nesmie vzniknúť pri inej stanici)
    //
    // Okolo KAŽDEJ stanice (tileID == 2) je graficky neviditeľná ochranná
    // zóna. Do tejto zóny nie je možné postaviť ďalšiu stanicu. Pravidlo je
    // kategória-agnostické: RAIL stanica blokuje aj ROAD stanicu a naopak
    // (kontroluje sa len tileID == 2, nie category).
    //
    // VEĽKOSŤ ZÓNY (STATION_EXCLUSION_RADIUS) je Chebyshev polomer okolo
    // stanice:
    //   1 → región 3×3 vycentrovaný na stanici (stanica + 8 susedov).
    //       Novú stanicu možno postaviť až vo vzdialenosti ≥ 2 tile, t.j.
    //       medzi dvoma stanicami ostane minimálne 1 voľný tile.
    //   2 → región 5×5 (väčší odstup – medzi stanicami min. 2 voľné tile).
    // Stačí zmeniť toto jediné číslo, zvyšok logiky sa prispôsobí.
    // =====================================================================

    const int STATION_EXCLUSION_RADIUS = 2;

    /// <summary>
    /// Vráti true, ak je daný RAIL režim umiestnením STANICE
    /// (StationHorizontal / StationVertical). Len pre tieto režimy sa
    /// kontroluje ochranná zóna okolo iných staníc.
    /// </summary>
    static bool IsRailStationMode(RailConstructionMode mode)
        => mode == RailConstructionMode.StationHorizontal
        || mode == RailConstructionMode.StationVertical;

    /// <summary>
    /// ROAD ekvivalent IsRailStationMode.
    /// </summary>
    static bool IsRoadStationMode(RoadConstructionMode mode)
        => mode == RoadConstructionMode.StationHorizontal
        || mode == RoadConstructionMode.StationVertical;

    /// <summary>
    /// Vráti true, ak je daný RAIL režim umiestnením DEPA (Depot* varianty).
    /// Len pre tieto režimy sa kontroluje vodorovnosť terénu (situácia č.2).
    /// </summary>
    static bool IsRailDepotMode(RailConstructionMode mode)
        => mode == RailConstructionMode.DepotHorizontalBottom
        || mode == RailConstructionMode.DepotVerticalBottom
        || mode == RailConstructionMode.DepotHorizontalTop
        || mode == RailConstructionMode.DepotVerticalTop;

    /// <summary>
    /// ROAD ekvivalent IsRailDepotMode.
    /// </summary>
    static bool IsRoadDepotMode(RoadConstructionMode mode)
        => mode == RoadConstructionMode.DepotHorizontalBottom
        || mode == RoadConstructionMode.DepotVerticalBottom
        || mode == RoadConstructionMode.DepotHorizontalTop
        || mode == RoadConstructionMode.DepotVerticalTop;

    // =====================================================================
    // KLASIFIKÁCIA KOĽAJOVÝCH/CESTNÝCH DIELOV PODĽA POVOLENÉHO TERÉNU
    //
    //   • ROVNÉ diely (Horizontal/Vertical) – smú na rovinu AJ na rampu.
    //   • OSTATNÉ track diely (crossroad, curves, switches) – len na rovinu.
    //
    // Stanice a depá majú vlastné guardy (vlastné hlášky), preto sú z
    // "flat-only track" skupiny vylúčené.
    // =====================================================================

    /// <summary>
    /// Vráti true, ak je RAIL režim ROVNÝ diel (RailHorizontal/RailVertical).
    /// Tieto sa smú stavať na rovine aj na šikmom tile (rampe).
    /// </summary>
    static bool IsRailStraightMode(RailConstructionMode mode)
        => mode == RailConstructionMode.RailHorizontal
        || mode == RailConstructionMode.RailVertical;

    /// <summary>
    /// ROAD ekvivalent IsRailStraightMode.
    /// </summary>
    static bool IsRoadStraightMode(RoadConstructionMode mode)
        => mode == RoadConstructionMode.RoadHorizontal
        || mode == RoadConstructionMode.RoadVertical;

    /// <summary>
    /// Vráti true pre RAIL track diel, ktorý sa smie stavať LEN na rovine –
    /// t.j. placement diel, ktorý NIE JE rovný, ani stanica, ani depo
    /// (zostávajú: crossroad, 4 curves, 4 switches).
    /// </summary>
    static bool IsRailFlatOnlyTrackMode(RailConstructionMode mode)
        => IsRailPlacementMode(mode)
        && !IsRailStraightMode(mode)
        && !IsRailStationMode(mode)
        && !IsRailDepotMode(mode);

    /// <summary>
    /// ROAD ekvivalent IsRailFlatOnlyTrackMode (RoadCrossroad, curves, switches).
    /// </summary>
    static bool IsRoadFlatOnlyTrackMode(RoadConstructionMode mode)
        => IsRoadPlacementMode(mode)
        && !IsRoadStraightMode(mode)
        && !IsRoadStationMode(mode)
        && !IsRoadDepotMode(mode);

    /// <summary>
    /// Vráti true, ak sa v okolí cieľového tile [cx,cz] (Chebyshev polomer
    /// STATION_EXCLUSION_RADIUS) už nachádza ĽUBOVOĽNÁ stanica (tileID == 2,
    /// RAIL aj ROAD). Slúži ako guard pred postavením novej stanice.
    ///
    /// Okrajové tiles mimo mriežky rieši GetTileByIndexAny – pre indexy mimo
    /// 0..GRID_SIZE-1 vracia prázdnu dlaždicu (tileID 0), takže sa nezarátajú.
    /// </summary>
    bool IsStationNearby(int cx, int cz)
    {
        if (IndAPI == null) return false;

        for (int dx = -STATION_EXCLUSION_RADIUS; dx <= STATION_EXCLUSION_RADIUS; dx++)
        {
            for (int dz = -STATION_EXCLUSION_RADIUS; dz <= STATION_EXCLUSION_RADIUS; dz++)
            {
                var t = IndAPI.GetTileByIndexAny(cx + dx, cz + dz);
                if (t.tileID == 2) // stanica (RAIL alebo ROAD)
                    return true;
            }
        }
        return false;
    }

    // =====================================================================
    // OCHRANNÁ ZÓNA TOVÁRNÍ (továreň nesmie vzniknúť pri inej továrni)
    //
    // Plná analógia k STATION_EXCLUSION_RADIUS / IsStationNearby, ale pre
    // viac-tile objekty (footprint). Okolo KAŽDEJ továrne (TileCategory.
    // Factory, tileID 4/5) je neviditeľná ochranná zóna. Do tejto zóny nie
    // je možné položiť ďalšiu továreň.
    //
    // FACTORY_EXCLUSION_RADIUS je Chebyshev polomer MERANÝ OD OKRAJA
    // footprintu plánovanej továrne (nie od stredu) – t.j. medzi dvoma
    // továrňami ostane minimálne toľko voľných tilov. Stačí zmeniť toto
    // jediné číslo, zvyšok logiky sa prispôsobí.
    // =====================================================================

    const int FACTORY_EXCLUSION_RADIUS = 3;

    /// <summary>
    /// Vráti true, ak sa v okolí PLÁNOVANÉHO footprintu továrne (ľavý-dolný
    /// roh [originX,originZ], rozmery width×depth) – rozšíreného o Chebyshev
    /// polomer FACTORY_EXCLUSION_RADIUS na všetky strany – už nachádza tile
    /// patriaci inej továrni (TileCategory.Factory). Slúži ako guard pred
    /// položením novej továrne (analógia k IsStationNearby).
    ///
    /// Tily samotného plánovaného footprintu sa preskakujú; okrajové indexy
    /// mimo mriežky rieši GetTileByIndexAny (vráti prázdnu dlaždicu).
    /// </summary>
    bool IsFactoryNearby(int originX, int originZ, int width, int depth)
    {
        if (IndAPI == null) return false;

        int minX = originX - FACTORY_EXCLUSION_RADIUS;
        int minZ = originZ - FACTORY_EXCLUSION_RADIUS;
        int maxX = originX + width - 1 + FACTORY_EXCLUSION_RADIUS;
        int maxZ = originZ + depth - 1 + FACTORY_EXCLUSION_RADIUS;

        for (int x = minX; x <= maxX; x++)
        {
            for (int z = minZ; z <= maxZ; z++)
            {
                // Vnútro plánovaného footprintu nie je "iná" továreň – preskoč.
                if (x >= originX && x < originX + width &&
                    z >= originZ && z < originZ + depth)
                    continue;

                var t = IndAPI.GetTileByIndexAny(x, z);
                if (t.category == IndicatrixAPI.TileCategory.Factory)
                    return true;
            }
        }
        return false;
    }

    /// <summary>
    /// Zdemoluje CELÚ továreň, ktorej footprint obsahuje kliknutý tile – nie
    /// len jeden tile. Vyčistí všetky tily footprintu z tile mapy
    /// (IndicatrixAPI.ClearTileByIndex) a odregistruje FactoryInstance z
    /// FactoryRegistry. Volá sa z RAIL aj ROAD Demolish vetvy potom, ako sa
    /// zistí, že kliknutý tile patrí kategórii Factory a továreň NIE JE vo
    /// výstavbe.
    ///
    /// REFUND: pripočíta 50 % z ceny továrne (ConstructionCosts.FactoryBuildCost
    /// = FactoryDefinition.Cost) – rovnaká politika ako pri RAIL/ROAD demolácii.
    /// Pri stavbe sa odpočítal plný Cost, pri demolácii sa vráti polovica
    /// (zaokrúhlené nadol, vždy z kladného základu). Refund je RAZ za celý
    /// objekt (nie per-tile).
    /// </summary>
    void DemolishFactory(FactoryInstance factory)
    {
        if (factory == null || IndAPI == null) return;

        for (int x = factory.OriginX; x < factory.OriginX + factory.Width; x++)
            for (int z = factory.OriginZ; z < factory.OriginZ + factory.Depth; z++)
                IndAPI.ClearTileByIndex(x, z);

        FactoryRegistry.Unregister(factory);

        // Refund 50 % z ceny továrne (raz za celý objekt).
        if (GameEconomy.instance != null && factory.Definition != null)
        {
            uint refund = ConstructionCosts.HalfRefund(
                ConstructionCosts.FactoryBuildCost(factory.Definition.Mode));
            if (refund > 0u) GameEconomy.instance.AddCredits(refund);
        }

        Debug.Log($"[GameManager] Továreň '{factory.Name}' zdemolovaná celá " +
                  $"({factory.Width}x{factory.Depth} tilov) z rohu " +
                  $"[{factory.OriginX},{factory.OriginZ}].");
    }

    /// <summary>
    /// Vráti true, ak by úprava terénu (LevelUp/LevelDown) klikom na vrchol
    /// snapPoint zmenila výšku aspoň jedného vrcholu patriaceho OBSADENÉMU
    /// tile (tileID != 0).
    ///
    /// Nestačí kontrolovať len klikaný vrchol: TerrainVertexLevel cez
    /// TerrainCollapse kaskádovo posúva aj okolité vrcholy. Preto si necháme
    /// od TerrainManager-a SIMULOVAŤ (dry-run) celú operáciu a vrátiť zoznam
    /// VŠETKÝCH vrcholov, ktoré by sa zmenili. Každý z nich potom overíme cez
    /// IndAPI.IsVertexOnOccupiedTile (vrchol je rohom až 4 tilov).
    ///
    /// Simulácia nič reálne nemení – terén sa upraví až keď táto metóda vráti
    /// false (žiadny dotknutý vrchol nepatrí obsadenému tile).
    /// </summary>
    bool WouldTerrainEditHitOccupied(Vector3 snapPoint, bool levelUp)
    {
        if (TerrainManager.instance == null || IndAPI == null) return false;

        var affected = TerrainManager.instance.PredictVertexLevelChanges(
            snapPoint.x, snapPoint.y, snapPoint.z, levelUp);

        for (int i = 0; i < affected.Count; i++)
        {
            if (IndAPI.IsVertexOnOccupiedTile(affected[i].x, affected[i].y))
                return true;
        }
        return false;
    }

    // =====================================================================
    // INICIALIZÁCIA
    // =====================================================================

    private void Awake()
    {
        instance = this;
    }

    private void Start()
    {
        cam = Camera.main;
        if (cam == null)
            cam = UnityEngine.Object.FindFirstObjectByType<Camera>();
    }

    // =====================================================================
    // UPDATE – KLÁVESOVÉ SKRATKY + KLIKANIE + POHYB MYŠI
    // =====================================================================

    private void Update()
    {
        HandleEscapeShortcut();

        if (cam == null) return; // bezpečnosť pred null kamerou
        if (IndAPI == null) return; // bezpečnosť pred neinicializovaným IndicatrixAPI

        Ray ray = cam.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, 100000))
        {
            // =========================================================
            // RAIL CONSTRUCTION MENU UI (pôvodná funkcionalita)
            // =========================================================

            if (CurrentRailConstructionMode != RailConstructionMode.None
                && trainInputMode == TrainInputMode.None)
            {
                // ON MOVEMENT
                if (!EventSystem.current.IsPointerOverGameObject())
                {
                    switch (CurrentRailConstructionMode)
                    {
                        case RailConstructionMode.LevelUp:
                        case RailConstructionMode.LevelDown:
                            IndAPI.SnapVertex(hit.point);
                            break;

                        case RailConstructionMode.Demolish:
                        case RailConstructionMode.StationHorizontal:
                        case RailConstructionMode.StationVertical:
                        case RailConstructionMode.RailHorizontal:
                        case RailConstructionMode.RailVertical:
                        case RailConstructionMode.RailCrossroad:
                        case RailConstructionMode.RailCurveRightBottom:
                        case RailConstructionMode.RailCurveLeftBottom:
                        case RailConstructionMode.RailCurveRightTop:
                        case RailConstructionMode.RailCurveLeftTop:
                        case RailConstructionMode.DepotHorizontalBottom:
                        case RailConstructionMode.DepotVerticalBottom:
                        case RailConstructionMode.DepotHorizontalTop:
                        case RailConstructionMode.DepotVerticalTop:
                        case RailConstructionMode.RailSwitchHorizontalBottom:
                        case RailConstructionMode.RailSwitchHorizontalTop:
                        case RailConstructionMode.RailSwitchVerticalBottom:
                        case RailConstructionMode.RailSwitchVerticalTop:
                            IndAPI.SnapLineFace(hit.point);
                            break;
                    }
                }

                // ON CLICK
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        // [ERROR GUARD] Stavba (placement) na UŽ OBSADENOM tile
                        // je zakázaná. Ak je aktuálny režim placement (kladie
                        // SetTile s tileID != 0) a cieľový tile už nie je
                        // prázdny, NIČ sa nepostaví a hráčovi sa zobrazí chyba.
                        // Demolish (SetTile = 0) a Level Up/Down sem nespadajú –
                        // IsRailPlacementMode ich vyradí.
                        if (IsRailPlacementMode(CurrentRailConstructionMode))
                        {
                            Vector2Int targetIdx = IndAPI.SnapTileIndex(hit.point);
                            var targetTile = IndAPI.GetTileByIndexAny(targetIdx.x, targetIdx.y);

                            // [ERROR GUARD] Na TOVÁREŇ (TileCategory.Factory) sa
                            // nesmie postaviť nič z RAIL výstavbových módov.
                            // Špecifickejšia hláška ako generická "occupied tile"
                            // nižšie – preto sa kontroluje skôr.
                            if (targetTile.category == IndicatrixAPI.TileCategory.Factory)
                            {
                                ReportError(GameErrors.CannotBuildOnExisting);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            if (targetTile.tileID != 0)
                            {
                                ReportError(GameErrors.CannotBuildOnOccupiedTile);
                                IndAPI.HideAllSnapVisuals();
                                return; // mutex režimy – ostatné vetvy sú neaktívne, návrat je bezpečný
                            }

                            // [ERROR GUARD] Stanica nesmie vzniknúť v ochrannej
                            // zóne (3×3) inej stanice. Kontrola je kategória-
                            // agnostická – blokuje RAIL aj ROAD stanicu v okolí.
                            // Len pre režimy umiestnenia stanice (tileID 2).
                            if (IsRailStationMode(CurrentRailConstructionMode)
                                && IsStationNearby(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildStationNearStation);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Stanica sa smie stavať len na
                            // VODOROVNOM tile (4 vertexy face s rovnakým Y).
                            if (IsRailStationMode(CurrentRailConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildStationOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Depo sa rovnako smie stavať len na
                            // vodorovnom tile.
                            if (IsRailDepotMode(CurrentRailConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildDepotOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] ROVNÝ koľajový diel (Horizontal/
                            // Vertical) sa smie stavať na rovine ALEBO na rampe.
                            if (IsRailStraightMode(CurrentRailConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y)
                                && !IndAPI.IsFaceRamp(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildTrackOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Ostatné koľajové diely (crossroad,
                            // curves, switches) sa smú stavať len na rovine.
                            if (IsRailFlatOnlyTrackMode(CurrentRailConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildTrackOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Dostatok kreditov. Cena sa odpočíta
                            // hneď tu (placement vetvy v switch už nezlyhajú),
                            // takže odpočet aj postavenie sú atomické.
                            if (!TryChargeRailBuild(CurrentRailConstructionMode))
                            {
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }
                        }

                        switch (CurrentRailConstructionMode)
                        {
                            case RailConstructionMode.LevelUp:
                                {
                                    Vector3 snapPoint = IndAPI.SnapVertex(hit.point);

                                    // [ERROR GUARD] Nepresiahnuť maximálnu úroveň
                                    // prevýšenia terénu.
                                    if (TerrainManager.instance.WouldExceedElevationLimit(snapPoint.y, true))
                                    {
                                        ReportError(GameErrors.MaxTerrainElevationReached);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    // [ERROR GUARD] Úprava terénu nesmie zmeniť
                                    // žiadny vrchol obsadeného tile – a to ani
                                    // cez kaskádu (TerrainCollapse), preto vopred
                                    // simulujeme celú operáciu.
                                    if (WouldTerrainEditHitOccupied(snapPoint, true))
                                    {
                                        ReportError(GameErrors.CannotEditTerrainOccupied);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    if (!TryChargeRailBuild(RailConstructionMode.LevelUp))
                                    {
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    TerrainManager.instance.TerrainVertexLevel(snapPoint.x, snapPoint.y, snapPoint.z, true);
                                }
                                break;

                            case RailConstructionMode.LevelDown:
                                {
                                    Vector3 snapPoint = IndAPI.SnapVertex(hit.point);

                                    // [ERROR GUARD] Nepodliezť minimálnu úroveň
                                    // prevýšenia terénu.
                                    if (TerrainManager.instance.WouldExceedElevationLimit(snapPoint.y, false))
                                    {
                                        ReportError(GameErrors.MinTerrainElevationReached);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    if (WouldTerrainEditHitOccupied(snapPoint, false))
                                    {
                                        ReportError(GameErrors.CannotEditTerrainOccupied);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    if (!TryChargeRailBuild(RailConstructionMode.LevelDown))
                                    {
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    TerrainManager.instance.TerrainVertexLevel(snapPoint.x, snapPoint.y, snapPoint.z, false);
                                }
                                break;

                            case RailConstructionMode.Demolish:
                                {
                                    Vector2Int tileIdx = IndAPI.SnapTileIndex(hit.point);
                                    IndicatrixAPI.TileData tileInfo = IndAPI.GetTileByIndexAny(tileIdx.x, tileIdx.y);

                                    // RAIL Demolish neničí ROAD tiles – tie sa
                                    // mažú cez ROAD menu (Demolish v ROAD režime).
                                    if (tileInfo.category == IndicatrixAPI.TileCategory.Road)
                                    {
                                        Debug.LogWarning($"[GameManager] RAIL Demolish ignorovaný – tile [{tileIdx.x},{tileIdx.y}] patrí ROAD systému. Použite ROAD menu.");
                                        ReportError(GameErrors.CannotDemolishWrongSystem);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    // TOVÁREŇ: demolish zmaže CELÚ továreň (celý
                                    // footprint), nie len kliknutý tile. Klik na
                                    // ľubovoľný tile footprintu nájde inštanciu cez
                                    // FactoryRegistry. Vo výstavbe sa demolovať
                                    // nesmie – až po dosiahnutí 100 % výstavby.
                                    if (tileInfo.category == IndicatrixAPI.TileCategory.Factory)
                                    {
                                        FactoryInstance fInst = FactoryRegistry.GetFactoryAt(tileIdx.x, tileIdx.y);
                                        if (fInst != null)
                                        {
                                            if (fInst.IsUnderConstruction)
                                            {
                                                Debug.LogWarning($"[GameManager] Demolish zamietnutý – továreň '{fInst.Name}' " +
                                                                 $"je vo výstavbe ({fInst.BuildPercent}%). Demoláciu skúste po dokončení.");
                                                IndAPI.HideAllSnapVisuals();
                                                break;
                                            }

                                            DemolishFactory(fInst);
                                            IndAPI.SnapMeshFace(hit.point);
                                            TrainSys?.OnMapChanged();
                                            break;
                                        }
                                    }

                                    // Depo nie je možné zmazať ak existuje vlak prináležiaci tomuto depu.
                                    // Depo je možné zmazať až po zmazaní vlaku cez DCRemoveTrainButton.
                                    if (tileInfo.tileID == 3)
                                    {
                                        var existingTrain = TrainSys?.GetTrain(tileIdx.x, tileIdx.y);
                                        if (existingTrain != null)
                                        {
                                            Debug.LogWarning($"[GameManager] Depo [{tileIdx.x},{tileIdx.y}] nie je možné zmazať – vlak stále existuje. Najprv vráťte vlak do depa a potom ho vymažte cez UI.");
                                            IndAPI.HideAllSnapVisuals();
                                            break;
                                        }
                                    }

                                    // Demolish – vymažeme dlaždicu
                                    IndAPI.SetTile(hit.point, 0, RailConstructionMode.None);
                                    IndAPI.SnapMeshFace(hit.point);
                                    TrainSys?.OnMapChanged();

                                    // Refund 50 % zo základnej ceny zdemolovaného
                                    // prvku (tileInfo bol prečítaný pred mazaním).
                                    RefundDemolishedTile(tileInfo);
                                }
                                break;

                            case RailConstructionMode.RailHorizontal:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailHorizontal);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailVertical:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailVertical);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailCrossroad:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailCrossroad);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RailConstructionMode.RailCurveLeftBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailCurveLeftBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailCurveRightBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailCurveRightBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailCurveLeftTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailCurveLeftTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailCurveRightTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailCurveRightTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailSwitchHorizontalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailSwitchHorizontalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailSwitchHorizontalTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailSwitchHorizontalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailSwitchVerticalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailSwitchVerticalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.RailSwitchVerticalTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RailConstructionMode.RailSwitchVerticalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RailConstructionMode.StationHorizontal:
                                {
                                    IndAPI.SetTile(hit.point, 2, RailConstructionMode.StationHorizontal);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.StationVertical:
                                {
                                    IndAPI.SetTile(hit.point, 2, RailConstructionMode.StationVertical);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RailConstructionMode.DepotHorizontalTop:
                                {
                                    IndAPI.SetTile(hit.point, 3, RailConstructionMode.DepotHorizontalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.DepotHorizontalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 3, RailConstructionMode.DepotHorizontalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.DepotVerticalTop:
                                {
                                    IndAPI.SetTile(hit.point, 3, RailConstructionMode.DepotVerticalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RailConstructionMode.DepotVerticalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 3, RailConstructionMode.DepotVerticalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                        }
                    }
                }
            }

            // =========================================================
            // ROAD CONSTRUCTION MENU UI (analógia k RAIL vetve)
            //
            // Spracovanie OnMovement a OnClick pre cestný systém. Volá
            // IndAPI.SetTile preťaženie pre RoadConstructionMode, ktoré
            // ukladá tile s TileCategory.Road (a tým je oddelené od RAIL
            // grafu, takže TrainSystem cestné tiles nevidí).
            // =========================================================

            if (CurrentRoadConstructionMode != RoadConstructionMode.None
                && trainInputMode == TrainInputMode.None)
            {
                // ON MOVEMENT
                if (!EventSystem.current.IsPointerOverGameObject())
                {
                    switch (CurrentRoadConstructionMode)
                    {
                        case RoadConstructionMode.LevelUp:
                        case RoadConstructionMode.LevelDown:
                            IndAPI.SnapVertex(hit.point);
                            break;

                        case RoadConstructionMode.Demolish:
                        case RoadConstructionMode.StationHorizontal:
                        case RoadConstructionMode.StationVertical:
                        case RoadConstructionMode.RoadHorizontal:
                        case RoadConstructionMode.RoadVertical:
                        case RoadConstructionMode.RoadCrossroad:
                        case RoadConstructionMode.RoadCurveRightBottom:
                        case RoadConstructionMode.RoadCurveLeftBottom:
                        case RoadConstructionMode.RoadCurveRightTop:
                        case RoadConstructionMode.RoadCurveLeftTop:
                        case RoadConstructionMode.DepotHorizontalBottom:
                        case RoadConstructionMode.DepotVerticalBottom:
                        case RoadConstructionMode.DepotHorizontalTop:
                        case RoadConstructionMode.DepotVerticalTop:
                        case RoadConstructionMode.RoadSwitchHorizontalBottom:
                        case RoadConstructionMode.RoadSwitchHorizontalTop:
                        case RoadConstructionMode.RoadSwitchVerticalBottom:
                        case RoadConstructionMode.RoadSwitchVerticalTop:
                            IndAPI.SnapLineFace(hit.point);
                            break;
                    }
                }

                // ON CLICK
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        // [ERROR GUARD] Rovnako ako pri RAIL: stavba (placement)
                        // na už obsadenom tile je zakázaná. Kontrola je
                        // kategória-agnostická (GetTileByIndexAny) – obsadený
                        // RAIL aj ROAD tile rovnako blokuje prepísanie.
                        if (IsRoadPlacementMode(CurrentRoadConstructionMode))
                        {
                            Vector2Int targetIdx = IndAPI.SnapTileIndex(hit.point);
                            var targetTile = IndAPI.GetTileByIndexAny(targetIdx.x, targetIdx.y);

                            // [ERROR GUARD] Na TOVÁREŇ (TileCategory.Factory) sa
                            // nesmie postaviť nič z ROAD výstavbových módov.
                            if (targetTile.category == IndicatrixAPI.TileCategory.Factory)
                            {
                                ReportError(GameErrors.CannotBuildOnExisting);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            if (targetTile.tileID != 0)
                            {
                                ReportError(GameErrors.CannotBuildOnOccupiedTile);
                                IndAPI.HideAllSnapVisuals();
                                return; // mutex režimy – ostatné vetvy sú neaktívne, návrat je bezpečný
                            }

                            // [ERROR GUARD] Stanica nesmie vzniknúť v ochrannej
                            // zóne (3×3) inej stanice. Rovnako ako pri RAIL je
                            // kontrola kategória-agnostická (blokuje RAIL aj ROAD
                            // stanicu v okolí). Len pre režimy stanice (tileID 2).
                            if (IsRoadStationMode(CurrentRoadConstructionMode)
                                && IsStationNearby(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildStationNearStation);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Stanica sa smie stavať len na
                            // VODOROVNOM tile (4 vertexy face s rovnakým Y).
                            if (IsRoadStationMode(CurrentRoadConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildStationOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Depo sa rovnako smie stavať len na
                            // vodorovnom tile.
                            if (IsRoadDepotMode(CurrentRoadConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildDepotOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] ROVNÝ cestný diel (Horizontal/
                            // Vertical) sa smie stavať na rovine ALEBO na rampe.
                            if (IsRoadStraightMode(CurrentRoadConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y)
                                && !IndAPI.IsFaceRamp(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildTrackOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Ostatné cestné diely (crossroad,
                            // curves, switches) sa smú stavať len na rovine.
                            if (IsRoadFlatOnlyTrackMode(CurrentRoadConstructionMode)
                                && !IndAPI.IsFaceFlat(targetIdx.x, targetIdx.y))
                            {
                                ReportError(GameErrors.CannotBuildTrackOnTerrain);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Dostatok kreditov (analógia k RAIL).
                            if (!TryChargeRoadBuild(CurrentRoadConstructionMode))
                            {
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }
                        }

                        switch (CurrentRoadConstructionMode)
                        {
                            case RoadConstructionMode.LevelUp:
                                {
                                    Vector3 snapPoint = IndAPI.SnapVertex(hit.point);

                                    // [ERROR GUARD] Nepresiahnuť maximálnu úroveň
                                    // prevýšenia terénu.
                                    if (TerrainManager.instance.WouldExceedElevationLimit(snapPoint.y, true))
                                    {
                                        ReportError(GameErrors.MaxTerrainElevationReached);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    // [ERROR GUARD] Rovnako ako pri RAIL –
                                    // simulujeme celú operáciu vrátane kaskády.
                                    if (WouldTerrainEditHitOccupied(snapPoint, true))
                                    {
                                        ReportError(GameErrors.CannotEditTerrainOccupied);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    if (!TryChargeRoadBuild(RoadConstructionMode.LevelUp))
                                    {
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    TerrainManager.instance.TerrainVertexLevel(snapPoint.x, snapPoint.y, snapPoint.z, true);
                                }
                                break;

                            case RoadConstructionMode.LevelDown:
                                {
                                    Vector3 snapPoint = IndAPI.SnapVertex(hit.point);

                                    // [ERROR GUARD] Nepodliezť minimálnu úroveň
                                    // prevýšenia terénu.
                                    if (TerrainManager.instance.WouldExceedElevationLimit(snapPoint.y, false))
                                    {
                                        ReportError(GameErrors.MinTerrainElevationReached);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    if (WouldTerrainEditHitOccupied(snapPoint, false))
                                    {
                                        ReportError(GameErrors.CannotEditTerrainOccupied);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    if (!TryChargeRoadBuild(RoadConstructionMode.LevelDown))
                                    {
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    TerrainManager.instance.TerrainVertexLevel(snapPoint.x, snapPoint.y, snapPoint.z, false);
                                }
                                break;

                            case RoadConstructionMode.Demolish:
                                {
                                    // Demolish v ROAD režime zmaže len cestnú dlaždicu
                                    // (rail dlaždice cez ROAD Demolish nemusí byť cieľom,
                                    // ale IndAPI.SetTile pre ROAD prepíše len Road kategóriu).
                                    // Ak by sme chceli aby ROAD Demolish nezasiahol RAIL tile,
                                    // pridáme kontrolu kategórie:
                                    Vector2Int rdTileIdx = IndAPI.SnapTileIndex(hit.point);
                                    var tileInfo = IndAPI.GetTileByIndexAny(rdTileIdx.x, rdTileIdx.y);

                                    if (tileInfo.category == IndicatrixAPI.TileCategory.Rail)
                                    {
                                        Debug.LogWarning("[GameManager] ROAD Demolish ignorovaný – tile patrí RAIL systému. Pre demoláciu RAIL prvkov použite RAIL menu.");
                                        ReportError(GameErrors.CannotDemolishWrongSystem);
                                        IndAPI.HideAllSnapVisuals();
                                        break;
                                    }

                                    // TOVÁREŇ: demolish zmaže CELÚ továreň (celý
                                    // footprint), nie len kliknutý tile – rovnako
                                    // ako v RAIL Demolish vetve. Vo výstavbe sa
                                    // demolovať nesmie.
                                    if (tileInfo.category == IndicatrixAPI.TileCategory.Factory)
                                    {
                                        FactoryInstance fInst = FactoryRegistry.GetFactoryAt(rdTileIdx.x, rdTileIdx.y);
                                        if (fInst != null)
                                        {
                                            if (fInst.IsUnderConstruction)
                                            {
                                                Debug.LogWarning($"[GameManager] Demolish zamietnutý – továreň '{fInst.Name}' " +
                                                                 $"je vo výstavbe ({fInst.BuildPercent}%). Demoláciu skúste po dokončení.");
                                                IndAPI.HideAllSnapVisuals();
                                                break;
                                            }

                                            DemolishFactory(fInst);
                                            IndAPI.SnapMeshFace(hit.point);
                                            VehicleSys?.OnMapChanged();
                                            break;
                                        }
                                    }

                                    // ROAD Depo nie je možné zmazať ak existuje vozidlo prináležiace
                                    // tomuto depu. Vozidlo treba najprv odstrániť cez UI
                                    // (DCRemoveVehicleButton v DepotRoadConstructionMenuUI).
                                    // Analogické správanie s RAIL Demolish vyššie.
                                    if (tileInfo.tileID == 3 && tileInfo.category == IndicatrixAPI.TileCategory.Road)
                                    {
                                        var existingVehicle = VehicleSys?.GetVehicle(rdTileIdx.x, rdTileIdx.y);
                                        if (existingVehicle != null)
                                        {
                                            Debug.LogWarning($"[GameManager] ROAD Depo [{rdTileIdx.x},{rdTileIdx.y}] nie je možné zmazať – vozidlo stále existuje. Najprv vráťte vozidlo do depa a potom ho vymažte cez UI.");
                                            IndAPI.HideAllSnapVisuals();
                                            break;
                                        }
                                    }

                                    IndAPI.SetTile(hit.point, 0, RoadConstructionMode.None);
                                    IndAPI.SnapMeshFace(hit.point);

                                    // Refund 50 % zo základnej ceny zdemolovaného
                                    // prvku (tileInfo bol prečítaný pred mazaním).
                                    RefundDemolishedTile(tileInfo);
                                }
                                break;

                            case RoadConstructionMode.RoadHorizontal:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadHorizontal);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadVertical:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadVertical);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadCrossroad:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadCrossroad);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RoadConstructionMode.RoadCurveLeftBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadCurveLeftBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadCurveRightBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadCurveRightBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadCurveLeftTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadCurveLeftTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadCurveRightTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadCurveRightTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RoadConstructionMode.RoadSwitchHorizontalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadSwitchHorizontalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadSwitchHorizontalTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadSwitchHorizontalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadSwitchVerticalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadSwitchVerticalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.RoadSwitchVerticalTop:
                                {
                                    IndAPI.SetTile(hit.point, 1, RoadConstructionMode.RoadSwitchVerticalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RoadConstructionMode.StationHorizontal:
                                {
                                    IndAPI.SetTile(hit.point, 2, RoadConstructionMode.StationHorizontal);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.StationVertical:
                                {
                                    IndAPI.SetTile(hit.point, 2, RoadConstructionMode.StationVertical);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;

                            case RoadConstructionMode.DepotHorizontalTop:
                                {
                                    IndAPI.SetTile(hit.point, 3, RoadConstructionMode.DepotHorizontalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.DepotHorizontalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 3, RoadConstructionMode.DepotHorizontalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.DepotVerticalTop:
                                {
                                    IndAPI.SetTile(hit.point, 3, RoadConstructionMode.DepotVerticalTop);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                            case RoadConstructionMode.DepotVerticalBottom:
                                {
                                    IndAPI.SetTile(hit.point, 3, RoadConstructionMode.DepotVerticalBottom);
                                    IndAPI.SnapMeshFace(hit.point);
                                }
                                break;
                        }

                        // Notifikácia pre VehicleSystem – akákoľvek modifikácia
                        // ROAD grafu (tile placement, Demolish) vyvolá debounce-d
                        // reroute pre bežiace vozidlá. LevelUp/LevelDown sem
                        // tiež spadnú (úprava terénu mení Y waypointov, ale
                        // grafovo nič – debounce timer to ošetrí, žiadne
                        // zbytočné prepočty pri rovine).
                        VehicleSys?.OnMapChanged();
                    }
                }
            }

            // =========================================================
            // FACTORY CONSTRUCTION MENU UI (analógia k RAIL/ROAD vetve)
            //
            // Spracovanie OnMovement a OnClick pre továrenský systém.
            //
            // ODLIŠNOSTI oproti RAIL/ROAD:
            //   - Továreň je VIAC-TILE objekt (footprint 2×3, 3×3, 2×2).
            //   - OnMovement používa IndAPI.SnapAreaFace(...) – zvýrazní
            //     celý obdĺžnik footprintu, nie len 1×1 tile.
            //   - OnClick volá multi-tile IndAPI.SetTile(...) FACTORY
            //     preťaženie, ktoré zapíše textúru do všetkých N tilov a
            //     vráti bool (úspech/zlyhanie kvôli kolízii alebo okraju).
            //   - tileID: 4 = Factory (CoalMine, Forest, IronOreMine, GoldMine,
            //                            SilverMine, Farm, OilWells),
            //             5 = Processing (PowerStation, SawMill, OilRefinery,
            //                            ElectronicsFactory, FurnitureFactory,
            //                            Slaughterhouse, GrainFactory, Smelter,
            //                            GlassFactory).
            // =========================================================

            if (CurrentFactoryConstructionMode != FactoryConstructionMode.None
                && trainInputMode == TrainInputMode.None)
            {
                // ROTÁCIA – klávesa R otočí továreň o 90°. Náhľad sa
                // prekreslí hneď nižšie v ON MOVEMENT vetve.
                if (Input.GetKeyDown(KeyCode.R))
                {
                    RotateFactory();
                }

                // ON MOVEMENT – náhľad footprintu pod kurzorom (s rotáciou)
                if (!EventSystem.current.IsPointerOverGameObject())
                {
                    IndAPI.SnapAreaFace(hit.point, CurrentFactoryConstructionMode,
                                        CurrentFactoryRotation);
                }

                // ON CLICK – umiestnenie továrne (s rotáciou)
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        // =================================================
                        // [ERROR GUARD] – PRED-VALIDÁCIA UMIESTNENIA TOVÁRNE
                        // Footprintovú matematiku počíta IndicatrixAPI (jediný
                        // zdroj pravdy). Najprv zistíme rozmery a roh footprintu
                        // pod kurzorom, potom overíme tri situácie a každú
                        // ohlásime VLASTNOU hláškou (SetTile vracia len bool a
                        // nevie POVEDAŤ, prečo by zlyhal).
                        // =================================================
                        if (IndAPI.TryGetFactoryFootprintBounds(
                                hit.point, CurrentFactoryConstructionMode, CurrentFactoryRotation,
                                out int fpOriginX, out int fpOriginZ, out int fpWidth, out int fpDepth))
                        {
                            // Klasifikácia obsadenosti footprintu:
                            //   • aspoň jeden tile je TOVÁREŇ   → továreň na továreň (situácia 2)
                            //   • aspoň jeden tile je RAIL/ROAD → továreň na inú stavbu (situácia 4)
                            bool footprintHasFactory = false;
                            bool footprintHasOther = false;

                            for (int x = fpOriginX; x < fpOriginX + fpWidth; x++)
                            {
                                for (int z = fpOriginZ; z < fpOriginZ + fpDepth; z++)
                                {
                                    var ft = IndAPI.GetTileByIndexAny(x, z);
                                    if (ft.tileID == 0) continue;

                                    if (ft.category == IndicatrixAPI.TileCategory.Factory)
                                        footprintHasFactory = true;
                                    else
                                        footprintHasOther = true;
                                }
                            }

                            // (2) Továreň NEMOŽNO postaviť na už existujúcu továreň.
                            if (footprintHasFactory)
                            {
                                ReportError(GameErrors.CannotBuildFactoryOnFactory);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // (4) Továreň NEMOŽNO postaviť na koľaj/cestu/stanicu/depo.
                            if (footprintHasOther)
                            {
                                ReportError(GameErrors.CannotBuildOnExisting);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // (3) Továreň NEMOŽNO postaviť v ochrannej zóne inej továrne.
                            if (IsFactoryNearby(fpOriginX, fpOriginZ, fpWidth, fpDepth))
                            {
                                ReportError(GameErrors.CannotBuildFactoryNearFactory);
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }

                            // [ERROR GUARD] Dostatok kreditov. Cena továrne
                            // (ConstructionCosts.FactoryBuildCost) sa odpočíta HNEĎ
                            // pri položení – hráč nečaká na 100 % výstavby. Geometria
                            // je už overená vyššie, takže nasledujúci SetTile už
                            // nezlyhá → odpočet a postavenie sú atomické.
                            if (!TryChargeFactoryBuild(CurrentFactoryConstructionMode))
                            {
                                IndAPI.HideAllSnapVisuals();
                                return;
                            }
                        }

                        bool placed = false;

                        // Footprint položenej továrne – vyplní ho rozšírené
                        // SetTile preťaženie cez out parametre. Slúži na
                        // následnú registráciu FactoryInstance.
                        int fOriginX = 0, fOriginZ = 0, fWidth = 0, fDepth = 0;

                        switch (CurrentFactoryConstructionMode)
                        {
                            // ── tileID = 4 → Factory (ťažba surovín) ──
                            case FactoryConstructionMode.CoalMine:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.CoalMine,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.Forest:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.Forest,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.IronOreMine:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.IronOreMine,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.GoldMine:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.GoldMine,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.SilverMine:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.SilverMine,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.Farm:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.Farm,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.OilWells:
                                placed = IndAPI.SetTile(hit.point, 4, FactoryConstructionMode.OilWells,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;

                            // ── tileID = 5 → Processing (spracovanie surovín) ──
                            case FactoryConstructionMode.PowerStation:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.PowerStation,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.SawMill:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.SawMill,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.OilRefinery:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.OilRefinery,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.ElectronicsFactory:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.ElectronicsFactory,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.FurnitureFactory:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.FurnitureFactory,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.Slaughterhouse:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.Slaughterhouse,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.GrainFactory:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.GrainFactory,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.Smelter:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.Smelter,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                            case FactoryConstructionMode.GlassFactory:
                                placed = IndAPI.SetTile(hit.point, 5, FactoryConstructionMode.GlassFactory,
                                                        out fOriginX, out fOriginZ, out fWidth, out fDepth,
                                                        CurrentFactoryRotation);
                                break;
                        }

                        // Po úspešnom položení:
                        //  1) zaevidujeme FactoryInstance vo FactoryRegistry –
                        //     vznikne dátový objekt továrne (Name, Load/UnLoad
                        //     sklady, pozícia footprintu). Tile mapa drží len
                        //     textúru; ekonomické dáta žijú tu.
                        //  2) obnovíme náhľad footprintu na pozícii kurzora.
                        if (placed)
                        {
                            FactoryDefinition def =
                                FactoryDatabase.GetDefinition(CurrentFactoryConstructionMode);

                            FactoryInstance newFactory =
                                FactoryRegistry.Register(def, fOriginX, fOriginZ,
                                                         fWidth, fDepth, CurrentFactoryRotation);

                            // DEFAULTNÉ hodnoty pri položení továrne na tile map:
                            // EmployeeSalary = 0, LevelSalary = 0, OccupancyFlag = false.
                            // Konštruktor FactoryInstance ich síce už nastavuje, no
                            // explicitne ich tu (priamo na mieste položenia) zaručíme aj
                            // z ovládacieho kódu, takže pri KAŽDOM položení sú garantované.
                            // ID typu (fixné, v poradí databázy) si inštancia prevzala
                            // z FactoryDefinition.ID už v konštruktore.
                            if (newFactory != null)
                                newFactory.ApplyPlacementDefaults();

                            // Spustíme fázu výstavby: nad stredom továrne sa v
                            // preddefinovanej výške Y zobrazí plávajúci label
                            // "Building process: NN%" a beží časovač s dĺžkou
                            // def.BuildingTime. Kým progres nedosiahne 100 %, je
                            // továreň zablokovaná pre zmeny hodnôt aj demoláciu;
                            // po 100 % label ešte 2 s zostane a potom zmizne.
                            if (newFactory != null)
                                FactoryConstructionManager.Instance.BeginConstruction(newFactory);

                            IndAPI.SnapAreaFace(hit.point, CurrentFactoryConstructionMode,
                                                CurrentFactoryRotation);
                        }
                        else
                        {
                            // FACTORY SetTile vráti false, ak je čo i len jeden
                            // tile footprintu obsadený alebo mimo gridu – t.j.
                            // "nedá sa tu stavať". Ohlásime tú istú chybu ako pri
                            // RAIL/ROAD (jedno univerzálne okno, rovnaký text).
                            ReportError(GameErrors.CannotBuildOnOccupiedTile);
                            IndAPI.HideAllSnapVisuals();
                        }
                    }
                }
            }

            // =========================================================
            // STAFF → FACTORY ASSIGNMENT (ConstructionModeStaffToFactory)
            //
            // Analógia k FACTORY vetve vyššie, ale namiesto stavania:
            //   • OnMovement – SnapMeshFace(...) zvýrazní 1×1 ŠTVOREC + FACE.
            //   • OnClick    – klik na ľubovoľnú továreň (footprint rieši
            //                  FactoryRegistry.GetFactoryAt) → priradenie /
            //                  porovnanie StaffManagement vs FactoryInstance.
            // =========================================================
            if (IsStaffToFactoryMode)
            {
                // ON MOVEMENT – štvorec + face pod kurzorom.
                if (!EventSystem.current.IsPointerOverGameObject())
                {
                    IndAPI.SnapMeshFace(hit.point);
                }

                // ON CLICK – klik na továreň.
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        HandleStaffToFactoryClick(hit.point);
                    }
                }
            }

            // =========================================================
            // VLAKOVÝ VSTUP – LEN AddStations (po stlačení DCDefineRouteButton)
            // =========================================================

            if (trainInputMode == TrainInputMode.AddStations)
            {
                // ON MOVEMENT
                if (!EventSystem.current.IsPointerOverGameObject())
                {
                    IndAPI.SnapLineFace(hit.point);
                }

                // ON CLICK – akceptujeme len kliky na stanice (depo už máme zapamätané)
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        HandleStationClick(hit.point);
                    }
                }
            }
            // =========================================================
            // CESTNÝ VSTUP – AddStationsRoad (po stlačení DCDefineRouteButton
            // v DepotRoadConstructionMenuUI). Analogická vetva k vlakovej
            // vyššie – akceptuje IBA kliky na ROAD Station tiles.
            // =========================================================
            else if (trainInputMode == TrainInputMode.AddStationsRoad)
            {
                // ON MOVEMENT
                if (!EventSystem.current.IsPointerOverGameObject())
                {
                    IndAPI.SnapLineFace(hit.point);
                }

                // ON CLICK – akceptujeme len kliky na ROAD stanice
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        HandleRoadStationClick(hit.point);
                    }
                }
            }
            else if (CurrentRailConstructionMode == RailConstructionMode.None
                     && CurrentRoadConstructionMode == RoadConstructionMode.None
                     && CurrentFactoryConstructionMode == FactoryConstructionMode.None
                     && !IsStaffToFactoryMode
                     && trainInputMode == TrainInputMode.None)
            {
                // =====================================================
                // OTVORENIE DEPOT / FACTORY MENU pri kliku na tile (mimo
                // akéhokoľvek režimu konštrukcie / vlakového či cestného
                // vstupu).
                //   • Klik na DEPOT tile (tileID 3) → podľa kategórie depa
                //     sa otvorí buď RAIL alebo ROAD variant Depot menu.
                //   • Klik na ľubovoľný tile FOOTPRINTU továrne → otvorí sa
                //     StatusFactoryMenuUI s informáciami o danej továrni.
                // =====================================================
                if (Input.GetMouseButtonDown(0))
                {
                    if (!EventSystem.current.IsPointerOverGameObject())
                    {
                        Vector2Int tileIdx = IndAPI.SnapTileIndex(hit.point);
                        var tileInfo = IndAPI.GetTileByIndexAny(tileIdx.x, tileIdx.y);
                        if (tileInfo.tileID == 3)
                        {
                            if (tileInfo.category == IndicatrixAPI.TileCategory.Rail)
                            {
                                DepotRailMenuUI?.OpenForDepot(tileIdx.x, tileIdx.y);
                            }
                            else if (tileInfo.category == IndicatrixAPI.TileCategory.Road)
                            {
                                DepotRoadMenuUI?.OpenForDepot(tileIdx.x, tileIdx.y);
                            }
                        }



                        else if (tileInfo.tileID == 2)
                        {
                            // [DIAG] Klik na stanicu – vetva trafená.
                            Debug.Log($"[DIAG][GameManager] Klik na tile [{tileIdx.x},{tileIdx.y}] " +
                                      $"tileID=2, category={tileInfo.category}.");

                            StationInstance station = null;
                            if (tileInfo.category == IndicatrixAPI.TileCategory.Rail)
                            {
                                // Lacný re-scan – istota, že register vidí všetky
                                // stanice z tile mapy + má aktuálne továrne v zóne.
                                RailStationRegistry.RescanAll();
                                Debug.Log($"[DIAG][GameManager] RailStationRegistry po Rescane: " +
                                          $"Count={RailStationRegistry.Count}");
                                station = RailStationRegistry.GetStationAt(tileIdx.x, tileIdx.y);
                            }
                            else if (tileInfo.category == IndicatrixAPI.TileCategory.Road)
                            {
                                RoadStationRegistry.RescanAll();
                                Debug.Log($"[DIAG][GameManager] RoadStationRegistry po Rescane: " +
                                          $"Count={RoadStationRegistry.Count}");
                                station = RoadStationRegistry.GetStationAt(tileIdx.x, tileIdx.y);
                            }
                            else
                            {
                                Debug.LogWarning($"[DIAG][GameManager] tileID==2 ale category={tileInfo.category} " +
                                                 $"– neznáma kategória stanice, ignorujem.");
                            }

                            if (station != null)
                            {
                                Debug.Log($"[DIAG][GameManager] StationInstance OK: {station}. " +
                                          $"Tovární v zóne: {station.Factories.Count}.");

                                var menuRef = StatusStationMenuUI;
                                if (menuRef != null)
                                {
                                    // Odovzdáme aj súradnice tile [x, z] stanice,
                                    // aby okno mohlo zobraziť názov "Station [x, z]".
                                    // tileIdx pochádza z IndAPI.SnapTileIndex →
                                    // Vector2Int(x, z), platí pre RAIL aj ROAD.
                                    menuRef.OpenForStation(station, tileIdx.x, tileIdx.y);
                                }
                                else
                                {
                                    Debug.LogError("[DIAG][GameManager] StatusStationMenuUI ref je NULL – " +
                                                   "komponent nie je v scéne!");
                                }
                            }
                            else
                            {
                                Debug.LogWarning($"[DIAG][GameManager] StationInstance pre [{tileIdx.x},{tileIdx.y}] " +
                                                 $"je null aj po Rescane. Skontroluj: IndicatrixAPI.GetTileByIndexAny " +
                                                 $"vrátil tileID={tileInfo.tileID}, category={tileInfo.category}, " +
                                                 $"ale register túto stanicu nevidí.");
                            }
                        }






                        else if (tileInfo.category == IndicatrixAPI.TileCategory.Factory)
                        {
                            // Továreň pozostáva z viac tilov (footprint 2×3,
                            // 3×3, 2×2 ...). FactoryRegistry pri položení
                            // zaregistroval KAŽDÝ tile footprintu, takže klik
                            // na ľubovoľný z nich nájde tú istú FactoryInstance
                            // v O(1). Footprint preto netreba mapovať ručne.
                            FactoryInstance factory =
                                FactoryRegistry.GetFactoryAt(tileIdx.x, tileIdx.y);
                            if (factory != null)
                            {
                                // Počas výstavby (progres < 100 %) je továreň
                                // zablokovaná – status okno neotvárame, aby sa
                                // nedali meniť množstvá (amount) ani kapacity.
                                if (factory.IsUnderConstruction)
                                {
                                    Debug.Log($"[GameManager] Továreň '{factory.Name}' je vo výstavbe " +
                                              $"({factory.BuildPercent}%) – zablokovaná, skúste po dokončení.");
                                }
                                else
                                {
                                    StatusFactoryMenuUI?.OpenForFactory(factory);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // =====================================================================
    // KLÁVESOVÉ SKRATKY – už len ESC
    // =====================================================================

    void HandleEscapeShortcut()
    {
        if (!Input.GetKeyDown(KeyCode.Escape)) return;

        // Celá reset logika je vyčlenená do verejnej metódy PerformEscapeReset(),
        // aby ju bolo možné vyvolať aj programovo (napr. po kliknutí na
        // CloseWindowButton ktoréhokoľvek konštrukčného okna) – nielen klávesou ESC.
        PerformEscapeReset();
    }

    /// <summary>
    /// Vykoná presne tú istú akciu ako stlačenie klávesy ESC: zruší aktuálny
    /// Staff→Factory / Define Route / RAIL / ROAD / FACTORY režim a schová
    /// všetky snap visuals (SnapLineFace, SnapVertex, SnapAreaFace, ...).
    ///
    /// Metóda je verejná a bezpečne volateľná z UI (napr. z OnCloseButtonClick
    /// jednotlivých *UIwindow skriptov), aby sa po zatvorení okna nezasekol
    /// snapping indikátor na tile mape. Je idempotentná – ak nie je aktívny
    /// žiadny režim, len defenzívne schová visuals.
    /// </summary>
    public void PerformEscapeReset()
    {
        bool didSomething = false;

        // ESC – Zrušenie režimu prideľovania personálu (Staff→Factory).
        if (IsStaffToFactoryMode)
        {
            ExitStaffToFactoryMode();
            Debug.Log("[GameManager] Staff→Factory režim zrušený cez ESC.");
            didSomething = true;
        }

        // ESC – Zrušenie aktuálneho vlakového ALEBO cestného režimu (Define Route)
        if (trainInputMode != TrainInputMode.None)
        {
            // Pre korektné notifikácie zachováme info o zrušenom režime
            // pred resetom – aby sme vedeli, ktoré UI okno notifikovať.
            bool wasRail = (trainInputMode == TrainInputMode.AddStations);
            bool wasRoad = (trainInputMode == TrainInputMode.AddStationsRoad);

            trainInputMode = TrainInputMode.None;
            collectingStations = false;
            pendingDepotTile = null;
            collectingRoadStations = false;
            pendingRoadDepotTile = null;
            IndAPI?.HideAllSnapVisuals();

            if (wasRail)
            {
                DepotRailMenuUI?.OnRouteDefineCancelled();
                Debug.Log("[GameManager] Vlakový režim (Define Route) zrušený cez ESC.");
            }
            else if (wasRoad)
            {
                DepotRoadMenuUI?.OnRouteDefineCancelled();
                Debug.Log("[GameManager] Cestný režim (Define Route) zrušený cez ESC.");
            }
            didSomething = true;
        }

        // ESC – Zrušenie aktuálneho režimu konštrukcie koľají
        // (Rail/Station/Depot/LevelUp/Demolish ...).
        // Toto rieši aj prípad, keď hráč zatvorí RailConstructionMenuUI okno
        // (cez X tlačidlo alebo iným spôsobom) bez toho, aby sa explicitne
        // resetoval režim – zaseknutý SnapLineFace indikátor zmizne.
        if (CurrentRailConstructionMode != RailConstructionMode.None)
        {
            SetTerrainMode(RailConstructionMode.None);
            IndAPI?.HideAllSnapVisuals();
            Debug.Log("[GameManager] RAIL konštrukčný režim zrušený cez ESC.");
            didSomething = true;
        }

        // ESC – Zrušenie aktuálneho ROAD režimu konštrukcie
        // Analogicky k RAIL bloku vyššie.
        if (CurrentRoadConstructionMode != RoadConstructionMode.None)
        {
            SetTerrainMode(RoadConstructionMode.None);
            IndAPI?.HideAllSnapVisuals();
            Debug.Log("[GameManager] ROAD konštrukčný režim zrušený cez ESC.");
            didSomething = true;
        }

        // ESC – Zrušenie aktuálneho FACTORY režimu konštrukcie
        // Analogicky k RAIL/ROAD blokom vyššie. Rieši aj prípad, keď hráč
        // zatvorí FactoryConstructionMenuUI okno bez explicitného resetu –
        // zaseknutý SnapAreaFace indikátor footprintu zmizne.
        if (CurrentFactoryConstructionMode != FactoryConstructionMode.None)
        {
            SetTerrainMode(FactoryConstructionMode.None);
            IndAPI?.HideAllSnapVisuals();
            Debug.Log("[GameManager] FACTORY konštrukčný režim zrušený cez ESC.");
            didSomething = true;
        }

        if (!didSomething)
        {
            // Defenzívne – aj keby niečo ostalo zaseknuté, schováme visuals
            IndAPI?.HideAllSnapVisuals();
        }
    }

    // =====================================================================
    // KLIK NA TOVÁREŇ PRI Staff→Factory PRIDEĽOVANÍ PERSONÁLU
    //
    // Volá sa z Update() v režime IsStaffToFactoryMode po kliku ĽAVÝM
    // tlačidlom mimo UI. Vyhodnotí, či sa kliklo na továreň, a podľa zhody
    // ID prenesie hodnoty z pendingStaff (StaffManagement) do FactoryInstance.
    // =====================================================================
    void HandleStaffToFactoryClick(Vector3 hitPoint)
    {
        if (IndAPI == null) return;

        // Bezpečnostná poistka – bez naplnenej dávky personálu nie je čo robiť.
        if (pendingStaff == null)
        {
            Debug.LogWarning("[GameManager] Staff→Factory: pendingStaff == null – režim ukončený.");
            ExitStaffToFactoryMode();
            return;
        }

        // Tile pod kurzorom (kategória-agnosticky – chceme vidieť aj továreň).
        Vector2Int tileIdx = IndAPI.SnapTileIndex(hitPoint);
        IndicatrixAPI.TileData tileInfo = IndAPI.GetTileByIndexAny(tileIdx.x, tileIdx.y);

        // Klik mimo továrne – ostávame v režime (môže kliknúť znova).
        if (tileInfo.category != IndicatrixAPI.TileCategory.Factory)
        {
            Debug.Log($"[GameManager] Staff→Factory: tile [{tileIdx.x},{tileIdx.y}] nie je továreň – ignorujem.");
            ReportError(GameErrors.StaffTargetNotAFactory);
            return;
        }

        // Footprint (2×3 / 3×3 / 2×2) je zohľadnený: GetFactoryAt mapuje
        // ktorýkoľvek tile footprintu na tú istú FactoryInstance.
        FactoryInstance factory = FactoryRegistry.GetFactoryAt(tileIdx.x, tileIdx.y);
        if (factory == null)
        {
            Debug.LogWarning($"[GameManager] Staff→Factory: na tile [{tileIdx.x},{tileIdx.y}] " +
                             $"je kategória Factory, ale FactoryRegistry továreň nevidí.");
            return;
        }

        // Vo výstavbe (progres < 100 %) – továreň je zablokovaná pre zmeny.
        if (factory.IsUnderConstruction)
        {
            Debug.Log($"[GameManager] Továreň '{factory.Name}' je vo výstavbe " +
                      $"({factory.BuildPercent}%) – personál nemožno priradiť. Skúste po dokončení.");
            return;
        }

        // ── POROVNANIE ID (StaffManagement vs FactoryInstance) ──
        if (factory.ID == pendingStaff.ID)
        {
            // Zhoda ID → prenes hodnoty a označ továreň ako obsadenú.
            factory.LevelSalary = pendingStaff.LevelSalary;
            factory.EmployeeSalary = pendingStaff.EmployeeSalary;
            factory.OccupancyFlag = true;

            Debug.Log($"[GameManager] Staff PRIRADENÝ do '{factory.Name}' " +
                      $"(ID={factory.ID}): LevelSalary={factory.LevelSalary}, " +
                      $"EmployeeSalary={factory.EmployeeSalary}, OccupancyFlag=true.");
        }
        else
        {
            // Nezhoda ID → len OccupancyFlag = false, ostatné polia bez zmeny.
            factory.OccupancyFlag = false;

            Debug.Log($"[GameManager] Staff ID={pendingStaff.ID} ≠ továreň '{factory.Name}' " +
                      $"ID={factory.ID} → OccupancyFlag=false (ostatné hodnoty nezmenené).");
        }

        // Jedno priradenie = jeden klik na továreň. Po vykonaní akcie režim
        // ukončíme a skryjeme snap vizuál.
        ExitStaffToFactoryMode();
    }

    // =====================================================================
    // KLIK NA STANICU PRI Define Route (analogicky pôvodnej W-fáze)
    // =====================================================================

    void HandleStationClick(Vector3 hitPoint)
    {
        if (IndAPI == null || TrainSys == null) return;
        if (!pendingDepotTile.HasValue) return;

        Vector2Int tile = IndAPI.SnapTileIndex(hitPoint);
        IndicatrixAPI.TileData tileData = IndAPI.GetTileByIndexAny(tile.x, tile.y);

        // Stanica musí byť RAIL kategórie – ROAD stanice (autobusové
        // zastávky) pre vlaky neplatia.
        if (tileData.tileID == 2 && tileData.category == IndicatrixAPI.TileCategory.Rail)
        {
            var depTile = pendingDepotTile.Value;
            bool ok = TrainSys.AddStation(depTile.x, depTile.y, tile.x, tile.y);
            if (ok)
            {
                Debug.Log($"[GameManager] Stanica [{tile.x},{tile.y}] pridaná do zoznamu.");
                DepotRailMenuUI?.OnStationAdded(depTile.x, depTile.y);
            }
            else
            {
                Debug.LogWarning($"[GameManager] Stanica [{tile.x},{tile.y}] nebola pridaná.");
            }
        }
        else
        {
            Debug.LogWarning("[GameManager] Kliknite na políčko RAIL Station! Pre ukončenie znovu stlačte tlačidlo Define Route alebo ESC.");
        }
    }

    /// <summary>
    /// ROAD analógia HandleStationClick. Akceptuje iba kliky na ROAD Station
    /// dlaždice (autobus./nákl. zastávky kategórie Road). RAIL stanice
    /// pre vozidlá neplatia.
    /// </summary>
    void HandleRoadStationClick(Vector3 hitPoint)
    {
        if (IndAPI == null || VehicleSys == null) return;
        if (!pendingRoadDepotTile.HasValue) return;

        Vector2Int tile = IndAPI.SnapTileIndex(hitPoint);
        IndicatrixAPI.TileData tileData = IndAPI.GetTileByIndexAny(tile.x, tile.y);

        if (tileData.tileID == 2 && tileData.category == IndicatrixAPI.TileCategory.Road)
        {
            var depTile = pendingRoadDepotTile.Value;
            bool ok = VehicleSys.AddStation(depTile.x, depTile.y, tile.x, tile.y);
            if (ok)
            {
                Debug.Log($"[GameManager] ROAD Stanica [{tile.x},{tile.y}] pridaná do zoznamu vozidla.");
                DepotRoadMenuUI?.OnStationAdded(depTile.x, depTile.y);
            }
            else
            {
                Debug.LogWarning($"[GameManager] ROAD Stanica [{tile.x},{tile.y}] nebola pridaná.");
            }
        }
        else
        {
            Debug.LogWarning("[GameManager] Kliknite na políčko ROAD Station! Pre ukončenie znovu stlačte tlačidlo Define Route alebo ESC.");
        }
    }

    // =====================================================================
    // PUBLIC API PRE DepotRailConstructionMenuUI
    //
    // Tieto metódy nahrádzajú pôvodné klávesové skratky Q, S, P, R, T.
    // UI volá konkrétnu metódu s identifikáciou depa (dx, dz), nad ktorým
    // bolo otvorené – takže nie je potrebný žiadny ďalší klik na tile mapu.
    // =====================================================================

    /// <summary>
    /// Q-ekvivalent: vytvorenie vlaku v zadanom depe.
    ///
    /// trainTypeIndex / wagonTypeIndex sú voľby z TrainTypeDropdown /
    /// WagonTypeDropdown – posúvajú sa do TrainSystem.CreateTrain, kde sa
    /// podľa nich zostaví dátová štruktúra súpravy (TrainStock.cs).
    /// </summary>
    public bool RequestCreateTrainForDepot(int dx, int dz, int wagonCount,
                                           int trainTypeIndex, int wagonTypeIndex)
    {
        if (TrainSys == null)
        {
            Debug.LogError("[GameManager] TrainSystem.instance je null!");
            return false;
        }
        if (IndAPI == null) return false;

        var tileInfo = IndAPI.GetTileByIndexAny(dx, dz);
        if (tileInfo.tileID != 3 || tileInfo.category != IndicatrixAPI.TileCategory.Rail)
        {
            Debug.LogWarning($"[GameManager] Tile [{dx},{dz}] nie je RAIL Depot.");
            return false;
        }

        // Aplikujeme aktuálne nastavený počet vagónov pre vlak
        int clamped = Mathf.Clamp(wagonCount, 1, 10);
        TrainSys.wagonCount = clamped;

        // EKONOMIKA: cena vlaku = Cost lokomotívy + clamped * Cost vagónu.
        // Odpočítame PRED vytvorením; ak vytvorenie zlyhá, sumu vrátime.
        uint trainCost = ConstructionCosts.TrainBuildCost(trainTypeIndex, wagonTypeIndex, clamped);
        if (GameEconomy.instance != null && !GameEconomy.instance.TrySpendCredits(trainCost))
        {
            ReportError($"Nedostatok kreditov – vlak stojí {trainCost} CR.");
            return false;
        }

        bool ok = TrainSys.CreateTrain(dx, dz, trainTypeIndex, wagonTypeIndex);
        if (ok)
        {
            Debug.Log($"[GameManager] Vlak vytvorený v depe [{dx},{dz}] s {clamped} vagónmi (cez UI).");

            // EVIDENCIA pre ročnú uzávierku – počet + reálne zaplatená cena vlaku.
            BudgetSystem.instance?.RecordTrainPurchase(trainCost);
        }
        else
        {
            // Vytvorenie sa nepodarilo (vlak už existuje / tile nie je Depot) –
            // vrátime odpočítanú sumu späť.
            if (GameEconomy.instance != null && trainCost > 0u)
                GameEconomy.instance.AddCredits(trainCost);
            Debug.LogWarning($"[GameManager] Vlak pre depo [{dx},{dz}] už existuje alebo tile nie je Depot.");
        }
        return ok;
    }

    /// <summary>
    /// Spätne kompatibilný preťažený podpis – vytvorí vlak s prvým typom
    /// vlaku aj vagónu (index 0). Pre volania bez voľby typu.
    /// </summary>
    public bool RequestCreateTrainForDepot(int dx, int dz, int wagonCount)
    {
        return RequestCreateTrainForDepot(dx, dz, wagonCount, 0, 0);
    }

    /// <summary>
    /// S-ekvivalent: spustenie vlaku v zadanom depe.
    /// </summary>
    public bool RequestStartTrainForDepot(int dx, int dz)
    {
        if (TrainSys == null) return false;
        bool ok = TrainSys.StartTrain(dx, dz);
        if (!ok)
            Debug.LogWarning($"[GameManager] Vlak z depa [{dx},{dz}] sa nedal spustiť (chýba vlak alebo stanice).");
        return ok;
    }

    /// <summary>
    /// P-ekvivalent: zastavenie vlaku.
    /// </summary>
    public bool RequestStopTrainForDepot(int dx, int dz)
    {
        if (TrainSys == null) return false;
        bool ok = TrainSys.StopTrain(dx, dz);
        if (!ok)
            Debug.LogWarning($"[GameManager] Vlak z depa [{dx},{dz}] sa nedal zastaviť.");
        return ok;
    }

    /// <summary>
    /// R-ekvivalent: návrat vlaku do depa.
    /// </summary>
    public TrainSystem.ReturnToDepotResult RequestSendToDepotForDepot(int dx, int dz)
    {
        if (TrainSys == null) return TrainSystem.ReturnToDepotResult.Error;
        var result = TrainSys.ReturnToDepot(dx, dz);
        if (result == TrainSystem.ReturnToDepotResult.StoppedAwaitingSecondR)
            Debug.Log($"[GameManager] Vlak z depa [{dx},{dz}] zastavil – žiadne dostupné stanice. Kliknite znovu Send To Depot pre priamy návrat.");
        else if (result == TrainSystem.ReturnToDepotResult.Error)
            Debug.LogWarning($"[GameManager] Vlak z depa [{dx},{dz}] sa nedal vrátiť do depa (chyba).");
        return result;
    }

    /// <summary>
    /// T-ekvivalent: odstránenie vlaku (musí byť v depe a zastavený).
    /// </summary>
    public bool RequestRemoveTrainForDepot(int dx, int dz)
    {
        if (TrainSys == null) return false;

        // EKONOMIKA: cenu súpravy (lokomotíva + všetky vagóny) spočítame PRED
        // odstránením, kým ešte existuje. Refund (50 %) pripočítame až po
        // úspešnom odstránení.
        int consistCost = 0;
        var td = TrainSys.GetTrain(dx, dz);
        if (td != null && td.consist != null)
        {
            consistCost = td.consist.Cost;
            foreach (var w in td.consist.Wagons)
                consistCost += w.Cost;
        }

        bool ok = TrainSys.RemoveTrain(dx, dz);
        if (ok)
        {
            if (GameEconomy.instance != null)
            {
                uint refund = ConstructionCosts.HalfRefund(consistCost);
                if (refund > 0u) GameEconomy.instance.AddCredits(refund);
            }
        }
        else
            Debug.LogWarning($"[GameManager] Vlak z depa [{dx},{dz}] nemôže byť odstránený (nie je v depe alebo beží).");
        return ok;
    }

    /// <summary>
    /// Vymaže všetky definované stanice pre vlak v zadanom depe a tiež zruší
    /// prípadne prebiehajúci "Define Route" režim pre toto depo (aby stanice
    /// nepribúdali do už-vyprázdneného zoznamu).
    ///
    /// POZNÁMKA: Táto metóda NEZASTAVÍ vlak. Volajúci (UI) je zodpovedný za
    /// to, aby vlak pred volaním zastavil cez RequestStopTrainForDepot v
    /// prípade, že vlak beží – inak by sa pohybová logika počas frame
    /// dostala k prázdnemu zoznamu staníc s nedefinovaným indexom.
    ///
    /// Vracia true ak boli stanice úspešne vyčistené (vlak existuje),
    /// false ak vlak v depe neexistuje.
    /// </summary>
    public bool RequestClearStationsForDepot(int dx, int dz)
    {
        if (TrainSys == null) return false;

        var td = TrainSys.GetTrain(dx, dz);
        if (td == null)
        {
            Debug.LogWarning($"[GameManager] V depe [{dx},{dz}] nie je vlak – nie je čo mazať.");
            return false;
        }

        // Ak práve zbierame stanice pre toto depo, najprv ukončíme zbieranie
        // (aby ďalšie kliky na mapu už nepridávali do vyčisteného zoznamu).
        if (trainInputMode == TrainInputMode.AddStations
            && collectingStations
            && pendingDepotTile.HasValue
            && pendingDepotTile.Value.x == dx
            && pendingDepotTile.Value.y == dz)
        {
            trainInputMode = TrainInputMode.None;
            collectingStations = false;
            pendingDepotTile = null;
            IndAPI?.HideAllSnapVisuals();
            Debug.Log("[GameManager] Define Route režim ukončený kvôli Delete All Routes.");
        }

        td.stations.Clear();
        td.currentStationIndex = 0;
        Debug.Log($"[GameManager] Stanice pre vlak v depe [{dx},{dz}] vymazané.");
        return true;
    }

    /// <summary>
    /// W-ekvivalent: prepnutie do/zo režimu definovania trasy (klikania na stanice).
    /// Volá sa pri stlačení DCDefineRouteButton.
    /// Vracia true ak práve začalo "collecting stations", false ak ho ukončilo.
    /// </summary>
    public bool RequestToggleDefineRouteForDepot(int dx, int dz)
    {
        if (TrainSys == null || IndAPI == null) return false;

        // Ak práve zbierame stanice pre rovnaké depo → ukončenie
        if (trainInputMode == TrainInputMode.AddStations
            && collectingStations
            && pendingDepotTile.HasValue
            && pendingDepotTile.Value.x == dx
            && pendingDepotTile.Value.y == dz)
        {
            trainInputMode = TrainInputMode.None;
            collectingStations = false;
            pendingDepotTile = null;
            IndAPI.HideAllSnapVisuals();
            Debug.Log("[GameManager] Define Route: Zadávanie staníc UKONČENÉ.");
            return false;
        }

        // Začiatok – overíme že v depe je vlak
        var td = TrainSys.GetTrain(dx, dz);
        if (td == null)
        {
            Debug.LogWarning($"[GameManager] V depe [{dx},{dz}] nie je vlak. Najprv vytvorte vlak.");
            return false;
        }

        // Aktivujeme režim AddStations rovno s vybraným depom (nepotrebujeme klik na depot)
        CurrentRailConstructionMode = RailConstructionMode.None;
        IndAPI.HideAllSnapVisuals();
        trainInputMode = TrainInputMode.AddStations;
        pendingDepotTile = new Vector2Int(dx, dz);
        collectingStations = true;
        td.stations.Clear();
        Debug.Log($"[GameManager] Define Route: Depo [{dx},{dz}] vybrané. Klikajte na Stanice. Ukončite opätovným stlačením Define Route alebo ESC.");
        return true;
    }

    // =====================================================================
    // PUBLIC API PRE DepotRoadConstructionMenuUI
    //
    // ROAD ekvivalent vyššie uvedeného API. Každá metóda volá VehicleSystem
    // namiesto TrainSystem a operuje nad pendingRoadDepotTile / collectingRoadStations
    // namiesto pendingDepotTile / collectingStations. Logika je inak zhodná.
    // =====================================================================

    /// <summary>
    /// Q-ekvivalent: vytvorenie vozidla v zadanom ROAD depe.
    ///
    /// Parameter vehicleTypeName (napr. "Vehicle 1", "Vehicle 2") je názov
    /// typu z VehicleCatalog (VehicleStock.cs). VehicleSystem podľa neho
    /// vyhľadá VehicleSpec a vytvorí dátovú štruktúru vozidla.
    /// </summary>
    public bool RequestCreateVehicleForRoadDepot(int dx, int dz, string vehicleTypeName)
    {
        if (VehicleSys == null)
        {
            Debug.LogError("[GameManager] VehicleSystem.instance je null!");
            return false;
        }
        if (IndAPI == null) return false;

        var tileInfo = IndAPI.GetTileByIndexAny(dx, dz);
        if (tileInfo.tileID != 3 || tileInfo.category != IndicatrixAPI.TileCategory.Road)
        {
            Debug.LogWarning($"[GameManager] Tile [{dx},{dz}] nie je ROAD Depot.");
            return false;
        }

        // EKONOMIKA: cena vozidla = Cost vozidla. Odpočítame PRED vytvorením;
        // pri zlyhaní sumu vrátime.
        uint vehicleCost = ConstructionCosts.VehicleBuildCost(vehicleTypeName);
        if (GameEconomy.instance != null && !GameEconomy.instance.TrySpendCredits(vehicleCost))
        {
            ReportError($"Nedostatok kreditov – vozidlo stojí {vehicleCost} CR.");
            return false;
        }

        bool ok = VehicleSys.CreateVehicle(dx, dz, vehicleTypeName);
        if (ok)
        {
            Debug.Log($"[GameManager] Vozidlo '{vehicleTypeName}' vytvorené v ROAD depe [{dx},{dz}] (cez UI).");

            // EVIDENCIA pre ročnú uzávierku – počet + reálne zaplatená cena vozidla.
            BudgetSystem.instance?.RecordVehiclePurchase(vehicleCost);
        }
        else
        {
            if (GameEconomy.instance != null && vehicleCost > 0u)
                GameEconomy.instance.AddCredits(vehicleCost);
            Debug.LogWarning($"[GameManager] Vozidlo pre ROAD depo [{dx},{dz}] už existuje alebo tile nie je ROAD Depot.");
        }
        return ok;
    }

    /// <summary>
    /// S-ekvivalent: spustenie vozidla v zadanom ROAD depe.
    /// </summary>
    public bool RequestStartVehicleForRoadDepot(int dx, int dz)
    {
        if (VehicleSys == null) return false;
        bool ok = VehicleSys.StartVehicle(dx, dz);
        if (!ok)
            Debug.LogWarning($"[GameManager] Vozidlo z ROAD depa [{dx},{dz}] sa nedalo spustiť (chýba vozidlo alebo stanice).");
        return ok;
    }

    /// <summary>
    /// P-ekvivalent: zastavenie vozidla.
    /// </summary>
    public bool RequestStopVehicleForRoadDepot(int dx, int dz)
    {
        if (VehicleSys == null) return false;
        bool ok = VehicleSys.StopVehicle(dx, dz);
        if (!ok)
            Debug.LogWarning($"[GameManager] Vozidlo z ROAD depa [{dx},{dz}] sa nedalo zastaviť.");
        return ok;
    }

    /// <summary>
    /// R-ekvivalent: návrat vozidla do depa.
    /// </summary>
    public VehicleSystem.ReturnToDepotResult RequestSendVehicleToDepotForRoadDepot(int dx, int dz)
    {
        if (VehicleSys == null) return VehicleSystem.ReturnToDepotResult.Error;
        var result = VehicleSys.ReturnToDepot(dx, dz);
        if (result == VehicleSystem.ReturnToDepotResult.StoppedAwaitingSecondR)
            Debug.Log($"[GameManager] Vozidlo z ROAD depa [{dx},{dz}] zastavilo – žiadne dostupné stanice. Kliknite znovu Send To Depot pre priamy návrat.");
        else if (result == VehicleSystem.ReturnToDepotResult.Error)
            Debug.LogWarning($"[GameManager] Vozidlo z ROAD depa [{dx},{dz}] sa nedalo vrátiť do depa (chyba).");
        return result;
    }

    /// <summary>
    /// T-ekvivalent: odstránenie vozidla (musí byť v depe a zastavené).
    /// </summary>
    public bool RequestRemoveVehicleForRoadDepot(int dx, int dz)
    {
        if (VehicleSys == null) return false;

        // EKONOMIKA: cenu vozidla spočítame PRED odstránením, refund (50 %)
        // pripočítame po úspešnom odstránení.
        int vehicleCost = 0;
        var vd = VehicleSys.GetVehicle(dx, dz);
        if (vd != null && vd.vehicleInstance != null)
            vehicleCost = vd.vehicleInstance.Cost;

        bool ok = VehicleSys.RemoveVehicle(dx, dz);
        if (ok)
        {
            if (GameEconomy.instance != null)
            {
                uint refund = ConstructionCosts.HalfRefund(vehicleCost);
                if (refund > 0u) GameEconomy.instance.AddCredits(refund);
            }
        }
        else
            Debug.LogWarning($"[GameManager] Vozidlo z ROAD depa [{dx},{dz}] nemôže byť odstránené (nie je v depe alebo beží).");
        return ok;
    }

    /// <summary>
    /// Vymaže všetky definované stanice pre vozidlo v zadanom ROAD depe a
    /// tiež zruší prípadne prebiehajúci "Define Route" režim pre toto depo
    /// (aby stanice nepribúdali do už-vyprázdneného zoznamu).
    ///
    /// POZNÁMKA: Táto metóda NEZASTAVÍ vozidlo. Volajúci (UI) je zodpovedný
    /// za to, aby vozidlo pred volaním zastavil cez RequestStopVehicleForRoadDepot
    /// v prípade, že vozidlo beží.
    /// </summary>
    public bool RequestClearStationsForRoadDepot(int dx, int dz)
    {
        if (VehicleSys == null) return false;

        var vd = VehicleSys.GetVehicle(dx, dz);
        if (vd == null)
        {
            Debug.LogWarning($"[GameManager] V ROAD depe [{dx},{dz}] nie je vozidlo – nie je čo mazať.");
            return false;
        }

        if (trainInputMode == TrainInputMode.AddStationsRoad
            && collectingRoadStations
            && pendingRoadDepotTile.HasValue
            && pendingRoadDepotTile.Value.x == dx
            && pendingRoadDepotTile.Value.y == dz)
        {
            trainInputMode = TrainInputMode.None;
            collectingRoadStations = false;
            pendingRoadDepotTile = null;
            IndAPI?.HideAllSnapVisuals();
            Debug.Log("[GameManager] ROAD Define Route režim ukončený kvôli Delete All Routes.");
        }

        vd.stations.Clear();
        vd.currentStationIndex = 0;
        Debug.Log($"[GameManager] Stanice pre vozidlo v ROAD depe [{dx},{dz}] vymazané.");
        return true;
    }

    /// <summary>
    /// W-ekvivalent: prepnutie do/zo režimu definovania trasy pre ROAD vozidlo.
    /// Volá sa pri stlačení DCDefineRouteButton v DepotRoadConstructionMenuUI.
    /// Vracia true ak práve začalo "collecting road stations", false ak ho ukončilo.
    /// </summary>
    public bool RequestToggleDefineRouteForRoadDepot(int dx, int dz)
    {
        if (VehicleSys == null || IndAPI == null) return false;

        // Ak práve zbierame stanice pre rovnaké depo → ukončenie
        if (trainInputMode == TrainInputMode.AddStationsRoad
            && collectingRoadStations
            && pendingRoadDepotTile.HasValue
            && pendingRoadDepotTile.Value.x == dx
            && pendingRoadDepotTile.Value.y == dz)
        {
            trainInputMode = TrainInputMode.None;
            collectingRoadStations = false;
            pendingRoadDepotTile = null;
            IndAPI.HideAllSnapVisuals();
            Debug.Log("[GameManager] ROAD Define Route: Zadávanie staníc UKONČENÉ.");
            return false;
        }

        // Začiatok – overíme že v depe je vozidlo
        var vd = VehicleSys.GetVehicle(dx, dz);
        if (vd == null)
        {
            Debug.LogWarning($"[GameManager] V ROAD depe [{dx},{dz}] nie je vozidlo. Najprv vytvorte vozidlo.");
            return false;
        }

        // Aktivujeme režim AddStationsRoad rovno s vybraným depom
        CurrentRoadConstructionMode = RoadConstructionMode.None;
        IndAPI.HideAllSnapVisuals();
        trainInputMode = TrainInputMode.AddStationsRoad;
        pendingRoadDepotTile = new Vector2Int(dx, dz);
        collectingRoadStations = true;
        vd.stations.Clear();
        Debug.Log($"[GameManager] ROAD Define Route: Depo [{dx},{dz}] vybrané. Klikajte na ROAD Stanice. Ukončite opätovným stlačením Define Route alebo ESC.");
        return true;
    }
}

IndicatrixAPI.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.InputSystem.HID;
using UnityEngine.Splines;


public class IndicatrixAPI : MonoBehaviour
{
    public static IndicatrixAPI instance;

    // =========================
    // DIRECTION MASK
    // =========================

    /// <summary>
    /// Smery, ktorými môže koľaj/dlaždica prepojiť susedov.
    /// Konvencia (zhodná s A* DIRECTIONS v TrainSystem):
    ///   Right  = +X (sused na východ)
    ///   Left   = -X (sused na západ)
    ///   Top    = +Z (sused na sever)
    ///   Bottom = -Z (sused na juh)
    /// </summary>
    [Flags]
    public enum DirectionMask
    {
        None = 0,
        Left = 1,
        Right = 2,
        Top = 4,
        Bottom = 8
    }

    /// <summary>
    /// Vráti opačný smer ku zadanému jednoduchému smeru.
    /// Pre kombinovaný mask vracia DirectionMask.None.
    /// </summary>
    public static DirectionMask Opposite(DirectionMask d)
    {
        switch (d)
        {
            case DirectionMask.Left: return DirectionMask.Right;
            case DirectionMask.Right: return DirectionMask.Left;
            case DirectionMask.Top: return DirectionMask.Bottom;
            case DirectionMask.Bottom: return DirectionMask.Top;
            default: return DirectionMask.None;
        }
    }

    // =========================
    // TILE CATEGORY (RAIL vs ROAD)
    // =========================

    /// <summary>
    /// Kategória dlaždice – odlišuje železničné a cestné prvky pri rovnakom
    /// tileID. Železnice a cesty zdieľajú jeden tile grid, ale TrainSystem
    /// si vyberá iba kategóriu Rail a (budúci) RoadSystem iba kategóriu Road.
    /// </summary>
    public enum TileCategory
    {
        None = 0,
        Rail = 1,
        Road = 2,
        Factory = 3   // Spoločná kategória pre Factory (tileID 4) aj Processing (tileID 5)
    }

    /// <summary>
    /// Mapuje RailConstructionMode (uložený ako stateID) na DirectionMask.
    /// Pre režimy, ktoré nie sú koľajové (None, LevelUp, ...) vracia None.
    /// </summary>
    public static DirectionMask GetConnectionsForMode(GameManager.RailConstructionMode mode)
    {
        switch (mode)
        {
            // Priame koľaje
            case GameManager.RailConstructionMode.RailHorizontal:
                return DirectionMask.Left | DirectionMask.Right;
            case GameManager.RailConstructionMode.RailVertical:
                return DirectionMask.Top | DirectionMask.Bottom;

            // Križovatka
            case GameManager.RailConstructionMode.RailCrossroad:
                return DirectionMask.Left | DirectionMask.Right | DirectionMask.Top | DirectionMask.Bottom;

            // Krivky (uhlové, ostré – bez vyhladenia)
            case GameManager.RailConstructionMode.RailCurveRightBottom:
                return DirectionMask.Right | DirectionMask.Bottom;
            case GameManager.RailConstructionMode.RailCurveLeftBottom:
                return DirectionMask.Left | DirectionMask.Bottom;
            case GameManager.RailConstructionMode.RailCurveRightTop:
                return DirectionMask.Right | DirectionMask.Top;
            case GameManager.RailConstructionMode.RailCurveLeftTop:
                return DirectionMask.Left | DirectionMask.Top;

            // Stanice
            case GameManager.RailConstructionMode.StationHorizontal:
                return DirectionMask.Left | DirectionMask.Right;
            case GameManager.RailConstructionMode.StationVertical:
                return DirectionMask.Top | DirectionMask.Bottom;

            // Depá
            case GameManager.RailConstructionMode.DepotHorizontalBottom:
            case GameManager.RailConstructionMode.DepotHorizontalTop:
                return DirectionMask.Left | DirectionMask.Right;
            case GameManager.RailConstructionMode.DepotVerticalBottom:
            case GameManager.RailConstructionMode.DepotVerticalTop:
                return DirectionMask.Top | DirectionMask.Bottom;

            // Výhybky (3-cestné, plne obojsmerné).
            // Každá výhybka má 1 priamy smer (Left↔Right alebo Top↔Bottom)
            // a 1 odbočný smer (kolmý). Všetky 3 spojenia môžu prechádzať
            // medzi sebou navzájom (žiadne asymetrické turnout).
            case GameManager.RailConstructionMode.RailSwitchHorizontalBottom:
                return DirectionMask.Left | DirectionMask.Right | DirectionMask.Bottom;
            case GameManager.RailConstructionMode.RailSwitchHorizontalTop:
                return DirectionMask.Left | DirectionMask.Right | DirectionMask.Top;
            case GameManager.RailConstructionMode.RailSwitchVerticalBottom:
                return DirectionMask.Top | DirectionMask.Bottom | DirectionMask.Right;
            case GameManager.RailConstructionMode.RailSwitchVerticalTop:
                return DirectionMask.Top | DirectionMask.Bottom | DirectionMask.Left;

            default:
                return DirectionMask.None;
        }
    }

    /// <summary>
    /// Mapuje RoadConstructionMode (uložený ako stateID) na DirectionMask.
    /// Úplne analogické k RAIL verzii vyššie.
    /// Pre režimy, ktoré nie sú cestné (None, LevelUp, ...) vracia None.
    /// </summary>
    public static DirectionMask GetConnectionsForMode(GameManager.RoadConstructionMode mode)
    {
        switch (mode)
        {
            // Priame cesty
            case GameManager.RoadConstructionMode.RoadHorizontal:
                return DirectionMask.Left | DirectionMask.Right;
            case GameManager.RoadConstructionMode.RoadVertical:
                return DirectionMask.Top | DirectionMask.Bottom;

            // Križovatka
            case GameManager.RoadConstructionMode.RoadCrossroad:
                return DirectionMask.Left | DirectionMask.Right | DirectionMask.Top | DirectionMask.Bottom;

            // Krivky
            case GameManager.RoadConstructionMode.RoadCurveRightBottom:
                return DirectionMask.Right | DirectionMask.Bottom;
            case GameManager.RoadConstructionMode.RoadCurveLeftBottom:
                return DirectionMask.Left | DirectionMask.Bottom;
            case GameManager.RoadConstructionMode.RoadCurveRightTop:
                return DirectionMask.Right | DirectionMask.Top;
            case GameManager.RoadConstructionMode.RoadCurveLeftTop:
                return DirectionMask.Left | DirectionMask.Top;

            // Stanice (autobusové / nákladné zastávky)
            case GameManager.RoadConstructionMode.StationHorizontal:
                return DirectionMask.Left | DirectionMask.Right;
            case GameManager.RoadConstructionMode.StationVertical:
                return DirectionMask.Top | DirectionMask.Bottom;

            // Depá (garáže)
            case GameManager.RoadConstructionMode.DepotHorizontalBottom:
            case GameManager.RoadConstructionMode.DepotHorizontalTop:
                return DirectionMask.Left | DirectionMask.Right;
            case GameManager.RoadConstructionMode.DepotVerticalBottom:
            case GameManager.RoadConstructionMode.DepotVerticalTop:
                return DirectionMask.Top | DirectionMask.Bottom;

            // T-križovatky / Y-rozdvojenia (3-cestné, plne obojsmerné)
            case GameManager.RoadConstructionMode.RoadSwitchHorizontalBottom:
                return DirectionMask.Left | DirectionMask.Right | DirectionMask.Bottom;
            case GameManager.RoadConstructionMode.RoadSwitchHorizontalTop:
                return DirectionMask.Left | DirectionMask.Right | DirectionMask.Top;
            case GameManager.RoadConstructionMode.RoadSwitchVerticalBottom:
                return DirectionMask.Top | DirectionMask.Bottom | DirectionMask.Right;
            case GameManager.RoadConstructionMode.RoadSwitchVerticalTop:
                return DirectionMask.Top | DirectionMask.Bottom | DirectionMask.Left;

            default:
                return DirectionMask.None;
        }
    }

    /// <summary>
    /// Mapuje FactoryConstructionMode na DirectionMask.
    ///
    /// POZN.: Továrne (Factory / Processing) NIE SÚ dopravný graf – nemajú
    /// koľajové/cestné prepojenia so susedmi. Sú to staticky umiestnené
    /// viac-tile objekty (footprint). Preto vždy vraciame DirectionMask.None.
    /// Toto preťaženie existuje hlavne kvôli konzistentnosti API (Load,
    /// SetTile, TileData konštruktor) – rovnako ako pri RAIL/ROAD.
    /// </summary>
    public static DirectionMask GetConnectionsForMode(GameManager.FactoryConstructionMode mode)
    {
        // Továrne nemajú smerové spojenia.
        return DirectionMask.None;
    }

    // =========================
    // TILE ENGINE
    // =========================

    [System.Serializable]
    public struct TileData
    {
        public int tileID;
        public int stateID;          // = (int)RailConstructionMode ALEBO (int)RoadConstructionMode
        public DirectionMask connections;
        public TileCategory category; // RAIL / ROAD / None

        public TileData(int t, int s, DirectionMask c, TileCategory cat)
        {
            tileID = t;
            stateID = s;
            connections = c;
            category = cat;
        }

        // Pôvodný konštruktor (3 parametre) – pre spätnú kompatibilitu predpokladá
        // RAIL kategóriu (pretože pred zavedením ROAD bolo všetko rail).
        public TileData(int t, int s, DirectionMask c)
        {
            tileID = t;
            stateID = s;
            connections = c;
            category = (t == 0) ? TileCategory.None : TileCategory.Rail;
        }

        // Spätne kompatibilný konštruktor – connections sa odvodí zo stateID.
        // Predpokladá RAIL kategóriu (zachovanie pôvodného správania).
        public TileData(int t, int s)
        {
            tileID = t;
            stateID = s;
            connections = GetConnectionsForMode((GameManager.RailConstructionMode)s);
            category = (t == 0) ? TileCategory.None : TileCategory.Rail;
        }
    }

    // =========================
    // FACTORY FOOTPRINT
    // =========================

    /// <summary>
    /// FactoryFootprint
    /// ─────────────────────────────────────────────────────────────────────
    /// Popisuje veľkosť (footprint) jednej továrne v tiloch a pozíciu
    /// "kurzorového" (anchor) tilu v rámci tohto obdĺžnika.
    ///
    /// width  = počet tilov v smere X
    /// depth  = počet tilov v smere Z
    /// anchorX, anchorZ = lokálny index tilu (0..width-1, 0..depth-1), nad
    ///                    ktorým je snap kurzor myši. Zvyšok footprintu sa
    ///                    odvodzuje relatívne od tohto tilu.
    ///
    /// PREČO ANCHOR:
    ///   Pre nepárny rozmer (napr. 3×3) existuje presný stredový tile.
    ///   Pre párny rozmer (napr. 2×3 alebo 2×2) geometrický stred padne na
    ///   hranu medzi tilmi – stredový tile neexistuje. Anchor preto definuje
    ///   fixný, predvídateľný tile, ktorý hráč "drží" pod myšou, takže
    ///   umiestnenie je konzistentné pri každej veľkosti.
    ///
    /// VÝPOČET ROHOV footprintu z anchor tilu (ax, az):
    ///   minX = ax - anchorX
    ///   minZ = az - anchorZ
    ///   maxX = minX + width  - 1
    ///   maxZ = minZ + depth  - 1
    /// </summary>
    /// <summary>
    /// FactoryRotation
    /// ─────────────────────────────────────────────────────────────────────
    /// Otočenie továrne na tile mape v krokoch po 90°.
    ///
    /// PREČO ENUM A NIE bool:
    ///   Bool (true/false) by stačil len na prepnutie 2×3 ↔ 3×2. Ale pri
    ///   nesymetrickom footprinte (2×3) chýba aj otočenie o 180° a 270°,
    ///   ktoré menia, KTORÝM smerom je továreň "otočená" (kde má vstup,
    ///   ktorý roh je anchor). Enum so 4 hodnotami pokrýva všetky prípady
    ///   a je ľahko rozšíriteľný.
    ///
    /// VPLYV NA FOOTPRINT:
    ///   - Deg0   / Deg180 → rozmery zostávajú width×depth.
    ///   - Deg90  / Deg270 → rozmery sa PREHODIA na depth×width
    ///     (to je presne tvoja predstava "rotation XZ / ZX").
    ///   Anchor (kurzorový tile) sa transformuje spolu s obdĺžnikom, takže
    ///   kurzor vždy "drží" ten istý logický tile továrne.
    /// </summary>
    public enum FactoryRotation
    {
        Deg0 = 0,   // bez rotácie (natívna orientácia z GetFactoryFootprint)
        Deg90 = 1,   // 90°  proti smeru hod. ručičiek – prehodí width↔depth
        Deg180 = 2,   // 180°
        Deg270 = 3    // 270° proti smeru hod. ručičiek – prehodí width↔depth
    }

    [System.Serializable]
    public struct FactoryFootprint
    {
        public int width;
        public int depth;
        public int anchorX;
        public int anchorZ;

        public FactoryFootprint(int width, int depth, int anchorX, int anchorZ)
        {
            this.width = width;
            this.depth = depth;
            this.anchorX = anchorX;
            this.anchorZ = anchorZ;
        }

        public int TileCount => width * depth;

        /// <summary>
        /// Vráti NOVÝ footprint otočený o zadaný uhol.
        ///
        /// Transformácia lokálnej súradnice (lx, lz) v rámci obdĺžnika
        /// width×depth do otočeného obdĺžnika:
        ///   Deg0:   (lx, lz)                  rozmer width×depth
        ///   Deg90:  (lz, width-1-lx)          rozmer depth×width
        ///   Deg180: (width-1-lx, depth-1-lz)  rozmer width×depth
        ///   Deg270: (depth-1-lz, lx)          rozmer depth×width
        /// Tá istá transformácia sa aplikuje na anchor, aby kurzor zostal
        /// nad rovnakým logickým tilom továrne.
        /// </summary>
        public FactoryFootprint Rotated(FactoryRotation rot)
        {
            switch (rot)
            {
                case FactoryRotation.Deg90:
                    // rozmer depth×width, anchor (anchorZ, width-1-anchorX)
                    return new FactoryFootprint(depth, width,
                                                anchorZ, width - 1 - anchorX);

                case FactoryRotation.Deg180:
                    // rozmer width×depth, anchor zrkadlený v oboch osiach
                    return new FactoryFootprint(width, depth,
                                                width - 1 - anchorX, depth - 1 - anchorZ);

                case FactoryRotation.Deg270:
                    // rozmer depth×width, anchor (depth-1-anchorZ, anchorX)
                    return new FactoryFootprint(depth, width,
                                                depth - 1 - anchorZ, anchorX);

                case FactoryRotation.Deg0:
                default:
                    return this;
            }
        }
    }

    /// <summary>
    /// Vráti footprint (veľkosť + anchor) pre zadaný typ továrne.
    ///
    /// VEĽKOSTI (podľa zadania):
    ///   Factory (tileID 4):
    ///     - Coal Mine       2×3
    ///     - Forest          3×3
    ///     - Iron Ore Mine   3×3
    ///     - Gold Mine       3×3
    ///     - Silver Mine     3×3
    ///     - Farm            3×3
    ///     - Oil Wells       3×3
    ///   Processing (tileID 5):
    ///     - Power Station       2×2
    ///     - Sawmill             2×2
    ///     - Oil Refinery        2×2
    ///     - Electronics Factory 3×3
    ///     - Furniture Factory   3×3
    ///     - Slaughterhouse      2×2
    ///     - Grain Factory       2×3
    ///     - Smelter             2×3
    ///     - Glass Factory       2×2
    ///
    /// ANCHOR: zvolený tak, aby kurzor bol čo najbližšie stredu obdĺžnika.
    ///   - Pre nepárny rozmer → presný stred (3 → index 1).
    ///   - Pre párny rozmer → "ľavý/dolný zo stredovej dvojice"
    ///     (2 → index 0), čím je footprint deterministický.
    ///
    /// Tieto hodnoty sú jediné miesto, kde sa veľkosti definujú – ide o
    /// "nastaviteľný parameter". Pre zmenu veľkosti továrne stačí upraviť
    /// príslušný riadok tu.
    /// </summary>
    public static FactoryFootprint GetFactoryFootprint(GameManager.FactoryConstructionMode mode)
    {
        switch (mode)
        {
            // ── Factory (tileID 4) ──

            // Coal Mine 2×3  → anchor (0,1)
            case GameManager.FactoryConstructionMode.CoalMine:
                return new FactoryFootprint(2, 3, 0, 1);

            // Forest 3×3 → anchor (1,1) = presný stred
            case GameManager.FactoryConstructionMode.Forest:
                return new FactoryFootprint(3, 3, 1, 1);

            // Iron Ore Mine 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.IronOreMine:
                return new FactoryFootprint(3, 3, 1, 1);

            // Gold Mine 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.GoldMine:
                return new FactoryFootprint(3, 3, 1, 1);

            // Silver Mine 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.SilverMine:
                return new FactoryFootprint(3, 3, 1, 1);

            // Farm 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.Farm:
                return new FactoryFootprint(3, 3, 1, 1);

            // Oil Wells 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.OilWells:
                return new FactoryFootprint(3, 3, 1, 1);

            // ── Processing (tileID 5) ──

            // Power Station 2×2 → anchor (0,0)
            case GameManager.FactoryConstructionMode.PowerStation:
                return new FactoryFootprint(2, 2, 0, 0);

            // Sawmill 2×2 → anchor (0,0)
            case GameManager.FactoryConstructionMode.SawMill:
                return new FactoryFootprint(2, 2, 0, 0);

            // Oil Refinery 2×2 → anchor (0,0)
            case GameManager.FactoryConstructionMode.OilRefinery:
                return new FactoryFootprint(2, 2, 0, 0);

            // Electronics Factory 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.ElectronicsFactory:
                return new FactoryFootprint(3, 3, 1, 1);

            // Furniture Factory 3×3 → anchor (1,1)
            case GameManager.FactoryConstructionMode.FurnitureFactory:
                return new FactoryFootprint(3, 3, 1, 1);

            // Slaughterhouse 2×2 → anchor (0,0)
            case GameManager.FactoryConstructionMode.Slaughterhouse:
                return new FactoryFootprint(2, 2, 0, 0);

            // Grain Factory 2×3 → anchor (0,1)
            case GameManager.FactoryConstructionMode.GrainFactory:
                return new FactoryFootprint(2, 3, 0, 1);

            // Smelter 2×3 → anchor (0,1)
            case GameManager.FactoryConstructionMode.Smelter:
                return new FactoryFootprint(2, 3, 0, 1);

            // Glass Factory 2×2 → anchor (0,0)
            case GameManager.FactoryConstructionMode.GlassFactory:
                return new FactoryFootprint(2, 2, 0, 0);

            // None / neznáme → 1×1 (degeneruje na klasický single-tile)
            default:
                return new FactoryFootprint(1, 1, 0, 0);
        }
    }

    /// <summary>
    /// Vráti footprint pre daný typ továrne UŽ OTOČENÝ o zadaný uhol.
    /// Pohodlné preťaženie – spojí GetFactoryFootprint(mode) a .Rotated(rot).
    /// </summary>
    public static FactoryFootprint GetFactoryFootprint(
        GameManager.FactoryConstructionMode mode, FactoryRotation rotation)
    {
        return GetFactoryFootprint(mode).Rotated(rotation);
    }

    const int GRID_SIZE = 256;
    TileData[,] tileGrid;

    // =========================
    // SNAP SYSTEM
    // =========================

    GameObject snapSphere;
    GameObject lineFace01, lineFace02, lineFace03, lineFace04;

    LineRenderer lineRenderer01, lineRenderer02, lineRenderer03, lineRenderer04;

    Material lineFaceMat, snapFaceMatTex;

    //Textúry pre RAIL
    Texture2D snapFaceTex_Rail;     //ID = 1
    Texture2D snapFaceTex_RailStation;  //ID = 2
    Texture2D snapFaceTex_RailDepot;    //ID = 3

    //Textúry pre ROAD
    Texture2D snapFaceTex_Road;     //ID = 1
    Texture2D snapFaceTex_RoadStation;  //ID = 2
    Texture2D snapFaceTex_RoadDepot;    //ID = 3

    //Textúry pre FACTORY
    Texture2D snapFaceTex_Factory;     //ID = 4
    Texture2D snapFaceTex_Processing;  //ID = 5

    Mesh meshFace;

    GameObject[,] tileMap;

    // =========================
    // INITIALIZATION
    // =========================

    private void Awake()
    {
        instance = this;
    }

    private void Start()
    {
        InitializeTileEngine();
        InitializeSnapSystem();
    }

    void InitializeTileEngine()
    {
        tileGrid = new TileData[GRID_SIZE, GRID_SIZE];
        tileMap = new GameObject[GRID_SIZE, GRID_SIZE];

        for (int x = 0; x < GRID_SIZE; x++)
        {
            for (int z = 0; z < GRID_SIZE; z++)
            {
                tileGrid[x, z] = new TileData(0, 0, DirectionMask.None, TileCategory.None);
                tileMap[x, z] = null;
            }
        }
    }

    void InitializeSnapSystem()
    {
        snapSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        Destroy(snapSphere.GetComponent<SphereCollider>());
        snapSphere.transform.localScale = Vector3.one * 0.15f;
        snapSphere.GetComponent<Renderer>().material.color = new Color(1, 1, 1, 0);

        lineFaceMat = Resources.Load<Material>("Materials/LineMat");
        snapFaceMatTex = Resources.Load<Material>("Materials/FaceMatTex");

        //Načítanie textúr pre RAIL
        snapFaceTex_Rail = Resources.Load<Texture2D>("Textures/Rail/Rail_Tex");
        snapFaceTex_RailStation = Resources.Load<Texture2D>("Textures/Rail/RailStation_Tex");
        snapFaceTex_RailDepot = Resources.Load<Texture2D>("Textures/Rail/RailDepot_Tex");

        //Načítanie textúr pre ROAD
        snapFaceTex_Road = Resources.Load<Texture2D>("Textures/Road/Road_Tex");
        snapFaceTex_RoadStation = Resources.Load<Texture2D>("Textures/Road/RoadStation_Tex");
        snapFaceTex_RoadDepot = Resources.Load<Texture2D>("Textures/Road/RoadDepot_Tex");

        //Načítanie textúr pre FACTORY
        snapFaceTex_Factory = Resources.Load<Texture2D>("Textures/Factory/Factory_Tex");
        snapFaceTex_Processing = Resources.Load<Texture2D>("Textures/Factory/Processing_Tex");

        meshFace = new Mesh();

        CreateLineRenderer(ref lineFace01, ref lineRenderer01, "LineFace01");
        CreateLineRenderer(ref lineFace02, ref lineRenderer02, "LineFace02");
        CreateLineRenderer(ref lineFace03, ref lineRenderer03, "LineFace03");
        CreateLineRenderer(ref lineFace04, ref lineRenderer04, "LineFace04");

        HideSnapSphere();
        HideSnapLines();
    }

    void HideSnapSphere()
    {
        if (snapSphere != null)
            snapSphere.SetActive(false);
    }

    void ShowSnapSphere()
    {
        if (snapSphere != null)
            snapSphere.SetActive(true);
    }

    void HideSnapLines()
    {
        if (lineFace01 != null) lineFace01.SetActive(false);
        if (lineFace02 != null) lineFace02.SetActive(false);
        if (lineFace03 != null) lineFace03.SetActive(false);
        if (lineFace04 != null) lineFace04.SetActive(false);
    }

    void ShowSnapLines()
    {
        if (lineFace01 != null) lineFace01.SetActive(true);
        if (lineFace02 != null) lineFace02.SetActive(true);
        if (lineFace03 != null) lineFace03.SetActive(true);
        if (lineFace04 != null) lineFace04.SetActive(true);
    }

    /// <summary>
    /// Skryje všetky snap vizuály (snap sphere aj línie). Volá sa pri prepnutí do vlakových režimov.
    /// </summary>
    public void HideAllSnapVisuals()
    {
        HideSnapSphere();
        HideSnapLines();
    }

    void CreateLineRenderer(ref GameObject obj, ref LineRenderer lr, string name)
    {
        obj = new GameObject(name);
        lr = obj.AddComponent<LineRenderer>();
        lr.material = lineFaceMat;
        lr.startWidth = 0.1f;
        lr.endWidth = 0.1f;
        lr.positionCount = 2;
    }

    // =========================
    // TILE ENGINE CORE
    // =========================

    public Vector2Int SnapTileIndex(Vector3 hitPoint)
    {
        int x = Mathf.FloorToInt(hitPoint.x);
        int z = Mathf.FloorToInt(hitPoint.z);

        return new Vector2Int(x, z);
    }

    /// <summary>
    /// Pôvodná signatúra – stateID 0 znamená "žiadny špecifický režim" (Demolish / clear).
    /// Connections sa nastaví na None. Predpokladá RAIL kategóriu (spätná kompatibilita).
    /// </summary>
    public void SetTile(Vector3 hitPoint, int tileID, int stateID)
    {
        SetTile(hitPoint, tileID, (GameManager.RailConstructionMode)stateID);
    }

    /// <summary>
    /// RAIL verzia – ukladá tileID + RailConstructionMode (ako stateID),
    /// connections sa odvodí cez GetConnectionsForMode, category = Rail
    /// (alebo None ak tileID == 0, t.j. Demolish/clear).
    /// </summary>
    public void SetTile(Vector3 hitPoint, int tileID, GameManager.RailConstructionMode mode)
    {
        Vector2Int index = SnapTileIndex(hitPoint);

        if (!IsValidIndex(index)) return;

        DirectionMask conns = (tileID == 0) ? DirectionMask.None : GetConnectionsForMode(mode);
        TileCategory cat = (tileID == 0) ? TileCategory.None : TileCategory.Rail;

        tileGrid[index.x, index.y] = new TileData(tileID, (int)mode, conns, cat);
        UpdateTileMap(index.x, index.y);
    }

    /// <summary>
    /// ROAD verzia – ukladá tileID + RoadConstructionMode (ako stateID),
    /// connections sa odvodí cez GetConnectionsForMode preťaženie pre Road,
    /// category = Road (alebo None ak tileID == 0).
    ///
    /// POZN.: stateID pre RAIL a ROAD pochádza z odlišných enum-ov, preto
    /// raw int by mohol kolidovať. Kategória je preto určujúca pri spätnej
    /// rekonštrukcii (pri Load alebo iných operáciách, ktoré poznajú len
    /// raw stateID).
    /// </summary>
    public void SetTile(Vector3 hitPoint, int tileID, GameManager.RoadConstructionMode mode)
    {
        Vector2Int index = SnapTileIndex(hitPoint);

        if (!IsValidIndex(index)) return;

        DirectionMask conns = (tileID == 0) ? DirectionMask.None : GetConnectionsForMode(mode);
        TileCategory cat = (tileID == 0) ? TileCategory.None : TileCategory.Road;

        tileGrid[index.x, index.y] = new TileData(tileID, (int)mode, conns, cat);
        UpdateTileMap(index.x, index.y);
    }

    /// <summary>
    /// FACTORY verzia – multi-tile umiestnenie továrne.
    ///
    /// Na rozdiel od RAIL/ROAD (vždy 1×1) zaberá továreň obdĺžnik N×M tilov
    /// podľa <see cref="GetFactoryFootprint"/>. Hit point určuje "anchor"
    /// tile (kurzor myši); zvyšok footprintu sa dopočíta okolo neho.
    ///
    /// Do KAŽDÉHO tilu footprintu sa zapíše tá istá TileData:
    ///   tileID   = 4 (Factory) alebo 5 (Processing)
    ///   stateID  = (int)FactoryConstructionMode (rozlíši CoalMine/Forest/...)
    ///   category = TileCategory.Factory
    ///   connections = None (továrne nie sú dopravný graf)
    ///
    /// Vďaka tomu sa textúra (Factory_Tex / Processing_Tex) vykreslí na
    /// všetkých tiloch footprintu (napr. 2×3 = 6 tilov).
    ///
    /// VALIDÁCIA: celý footprint musí byť (a) vnútri gridu a (b) prázdny
    /// (samé tileID == 0). Ak čokoľvek prekáža, NIČ sa nepoloží a metóda
    /// vráti false – továreň sa nepoloží "čiastočne".
    ///
    /// stateID == 0 (FactoryConstructionMode.None) sa berie ako "clear" –
    /// vymaže 1×1 tile pod kurzorom (rovnaká sémantika ako Demolish).
    ///
    /// PARAMETER rotation:
    ///   Otočenie továrne o 0/90/180/270°. Pri 90°/270° sa footprint
    ///   prehodí (width↔depth). Anchor sa transformuje spolu s obdĺžnikom,
    ///   takže kurzor drží stále ten istý logický tile. Default = Deg0
    ///   (bez rotácie) – staré volania bez tohto parametra fungujú ďalej.
    /// </summary>
    public bool SetTile(Vector3 hitPoint, int tileID, GameManager.FactoryConstructionMode mode,
                        FactoryRotation rotation = FactoryRotation.Deg0)
    {
        // Deleguje na preťaženie s out parametrami; výsledný footprint sa
        // tu zahodí (volajúci ho nepotrebuje).
        return SetTile(hitPoint, tileID, mode, out _, out _, out _, out _, rotation);
    }

    /// <summary>
    /// FACTORY verzia s VÝSTUPOM footprintu – funkčne identická s preťažením
    /// vyššie, navyše ale cez out parametre oznámi, KAM presne sa továreň
    /// položila (ľavý-dolný roh + rozmery už po rotácii).
    ///
    /// Vďaka tomu vie volajúci (GameManager) zaevidovať FactoryInstance bez
    /// toho, aby duplikoval výpočet footprintu – jediný zdroj pravdy pre
    /// pozíciu zostáva tu.
    ///
    /// Pri neúspechu (mimo gridu / kolízia) sú out parametre nastavené na 0
    /// a metóda vráti false. Pri None/clear vetve out parametre popisujú
    /// vymazaný 1×1 tile (origin = anchor, width = depth = 1).
    /// </summary>
    public bool SetTile(Vector3 hitPoint, int tileID, GameManager.FactoryConstructionMode mode,
                        out int originX, out int originZ, out int width, out int depth,
                        FactoryRotation rotation = FactoryRotation.Deg0)
    {
        originX = 0; originZ = 0; width = 0; depth = 0;

        Vector2Int anchor = SnapTileIndex(hitPoint);
        if (!IsValidIndex(anchor)) return false;

        // None / clear → vymaž 1 tile pod kurzorom.
        if (tileID == 0 || mode == GameManager.FactoryConstructionMode.None)
        {
            tileGrid[anchor.x, anchor.y] = new TileData(0, 0, DirectionMask.None, TileCategory.None);
            UpdateTileMap(anchor.x, anchor.y);
            originX = anchor.x; originZ = anchor.y; width = 1; depth = 1;
            return true;
        }

        // Footprint UŽ otočený podľa rotation.
        FactoryFootprint fp = GetFactoryFootprint(mode, rotation);

        // Ľavý-dolný roh footprintu odvodený z anchor tilu.
        int minX = anchor.x - fp.anchorX;
        int minZ = anchor.y - fp.anchorZ;
        int maxX = minX + fp.width - 1;
        int maxZ = minZ + fp.depth - 1;

        // (a) celý footprint musí byť vnútri gridu
        if (minX < 0 || minZ < 0 || maxX >= GRID_SIZE || maxZ >= GRID_SIZE)
        {
            Debug.LogWarning($"[IndicatrixAPI] Factory '{mode}' ({fp.width}x{fp.depth}) " +
                             $"presahuje okraj mapy – neumiestnené.");
            return false;
        }

        // (b) celý footprint musí byť prázdny
        for (int x = minX; x <= maxX; x++)
        {
            for (int z = minZ; z <= maxZ; z++)
            {
                if (tileGrid[x, z].tileID != 0)
                {
                    Debug.LogWarning($"[IndicatrixAPI] Factory '{mode}' – tile [{x},{z}] " +
                                     $"je obsadený. Footprint musí byť celý prázdny – neumiestnené.");
                    return false;
                }
            }
        }

        // Zápis tej istej TileData do všetkých tilov footprintu.
        DirectionMask conns = GetConnectionsForMode(mode); // None
        for (int x = minX; x <= maxX; x++)
        {
            for (int z = minZ; z <= maxZ; z++)
            {
                tileGrid[x, z] = new TileData(tileID, (int)mode, conns, TileCategory.Factory);
                UpdateTileMap(x, z);
            }
        }

        // Výstup položeného footprintu (po rotácii).
        originX = minX; originZ = minZ; width = fp.width; depth = fp.depth;

        Debug.Log($"[IndicatrixAPI] Factory '{mode}' (rot {rotation}) umiestnená na footprint " +
                  $"[{minX},{minZ}]–[{maxX},{maxZ}] ({fp.TileCount} tilov).");
        return true;
    }

    /// <summary>
    /// Vymaže JEDEN tile na danom indexe [x,z] (nastaví prázdnu TileData,
    /// tileID 0, category None) a prekreslí jeho mesh cez UpdateTileMap.
    ///
    /// Na rozdiel od SetTile(Vector3 ...) pracuje priamo s tile indexom, takže
    /// volajúci (napr. GameManager.DemolishFactory) môže v cykle vyčistiť celý
    /// footprint viac-tile objektu (továrne) bez prepočtu svetových súradníc.
    /// Index mimo mriežky sa bezpečne ignoruje.
    /// </summary>
    public void ClearTileByIndex(int x, int z)
    {
        if (!IsValidIndex(new Vector2Int(x, z))) return;

        tileGrid[x, z] = new TileData(0, 0, DirectionMask.None, TileCategory.None);
        UpdateTileMap(x, z);
    }

    /// <summary>
    /// Vypočíta (BEZ akéhokoľvek zápisu do mapy) obdĺžnik footprintu, ktorý by
    /// zabrala továreň typu <paramref name="mode"/> položená pod kurzorom
    /// <paramref name="hitPoint"/> s rotáciou <paramref name="rotation"/>.
    ///
    /// Slúži na PRED-validáciu v GameManager-i (kontrola obsadenosti footprintu
    /// a ochrannej zóny okolo iných tovární) – footprintová matematika tak
    /// zostáva na jedinom mieste (zhodná s tým, čo neskôr spraví SetTile).
    ///
    /// Vráti true, ak je celý footprint vnútri gridu; out parametre potom držia
    /// ľavý-dolný roh a rozmery (už po rotácii). Pri footprinte mimo mapy vráti
    /// false a out parametre sú 0.
    /// </summary>
    public bool TryGetFactoryFootprintBounds(
        Vector3 hitPoint, GameManager.FactoryConstructionMode mode, FactoryRotation rotation,
        out int originX, out int originZ, out int width, out int depth)
    {
        originX = 0; originZ = 0; width = 0; depth = 0;

        Vector2Int anchor = SnapTileIndex(hitPoint);
        if (!IsValidIndex(anchor)) return false;
        if (mode == GameManager.FactoryConstructionMode.None) return false;

        FactoryFootprint fp = GetFactoryFootprint(mode, rotation);

        int minX = anchor.x - fp.anchorX;
        int minZ = anchor.y - fp.anchorZ;
        int maxX = minX + fp.width - 1;
        int maxZ = minZ + fp.depth - 1;

        if (minX < 0 || minZ < 0 || maxX >= GRID_SIZE || maxZ >= GRID_SIZE)
            return false;

        originX = minX; originZ = minZ; width = fp.width; depth = fp.depth;
        return true;
    }

    /// <summary>
    /// Vráti tile dáta pre daný hit point.
    ///
    /// KRITICKÉ: Táto metóda RETURNS ONLY RAIL tiles. Ak je na pozícii ROAD
    /// tile, vráti prázdnu TileData (tileID=0). Týmto sa zabezpečuje, že
    /// existujúci kód (predovšetkým TrainSystem.cs a jeho A* algoritmus)
    /// vidí len RAIL graf a nemusí byť modifikovaný kvôli ROAD systému.
    ///
    /// Pre prístup ku všetkým tile dátam (bez ohľadu na kategóriu) použite
    /// <see cref="GetTileAny"/>.
    /// </summary>
    public TileData GetTile(Vector3 hitPoint)
    {
        Vector2Int index = SnapTileIndex(hitPoint);

        if (!IsValidIndex(index))
            return new TileData(0, 0, DirectionMask.None, TileCategory.None);

        var td = tileGrid[index.x, index.y];
        if (td.category == TileCategory.Road)
            return new TileData(0, 0, DirectionMask.None, TileCategory.None);

        return td;
    }

    /// <summary>
    /// Vráti tile dáta pre daný hit point BEZ filtra kategórie. Použije sa
    /// keď volajúci potrebuje vidieť všetky tile dáta (napr. GameManager
    /// pri Demolish overuje kategóriu, alebo budúci RoadSystem).
    /// </summary>
    public TileData GetTileAny(Vector3 hitPoint)
    {
        Vector2Int index = SnapTileIndex(hitPoint);

        if (!IsValidIndex(index))
            return new TileData(0, 0, DirectionMask.None, TileCategory.None);

        return tileGrid[index.x, index.y];
    }

    /// <summary>
    /// Priamy prístup k tileGrid cez tile indexy (pre TrainSystem a A*).
    ///
    /// KRITICKÉ: Vracia len RAIL tiles (rovnaká logika ako GetTile vyššie).
    /// ROAD tiles sa javia ako prázdne (tileID=0). Týmto je TrainSystem.cs
    /// úplne izolovaný od ROAD systému.
    /// </summary>
    public TileData GetTileByIndex(int x, int z)
    {
        if (x < 0 || x >= GRID_SIZE || z < 0 || z >= GRID_SIZE)
            return new TileData(0, 0, DirectionMask.None, TileCategory.None);

        var td = tileGrid[x, z];
        if (td.category == TileCategory.Road)
            return new TileData(0, 0, DirectionMask.None, TileCategory.None);

        return td;
    }

    /// <summary>
    /// Priamy prístup k tileGrid cez tile indexy BEZ filtra kategórie.
    /// Použije sa keď volajúci potrebuje vidieť všetky tile dáta vrátane ROAD
    /// (napr. GameManager kontroluje kategóriu pred Demolish, alebo budúci
    /// RoadSystem.cs si bude takto čítať road graf).
    /// </summary>
    public TileData GetTileByIndexAny(int x, int z)
    {
        if (x < 0 || x >= GRID_SIZE || z < 0 || z >= GRID_SIZE)
            return new TileData(0, 0, DirectionMask.None, TileCategory.None);

        return tileGrid[x, z];
    }

    // =========================
    // TERÉNNE DOTAZY (rovinatosť face, vrchol vs. obsadené tiles)
    //
    // Pomocné read-only metódy pre GameManager error-guardy:
    //   • IsFaceFlat            – je tile vodorovný (4 vertexy rovnaké Y)?
    //   • IsVertexOnOccupiedTile – patrí vrchol niektorému obsadenému tile?
    // =========================

    /// <summary>
    /// Vráti true, ak je face tile [tileX, tileZ] VODOROVNÝ – t.j. všetky
    /// 4 rohové vertexy majú rovnaké Y. Používa FunctionalValueY (číta výšky
    /// vertexov z TerrainManager.coordsF), rovnako ako SnapMeshFace.
    ///
    /// Rohy tile [x,z]: (x,z), (x,z+1), (x+1,z+1), (x+1,z).
    ///
    /// Slúži ako guard pre stavbu staníc/dep – tie sa smú stavať len na rovine.
    /// Ak TerrainManager ešte nie je inicializovaný, vracia true (fail-open –
    /// guard vtedy nebráni stavbe; reálne sa volá až počas hry, keď terén beží).
    /// </summary>
    public bool IsFaceFlat(int tileX, int tileZ)
    {
        if (TerrainManager.instance == null) return true;

        float y0 = FunctionalValueY(tileX, tileZ);
        float y1 = FunctionalValueY(tileX, tileZ + 1);
        float y2 = FunctionalValueY(tileX + 1, tileZ + 1);
        float y3 = FunctionalValueY(tileX + 1, tileZ);

        const float EPS = 0.0001f; // tolerancia na float nepresnosti
        return Mathf.Abs(y0 - y1) < EPS
            && Mathf.Abs(y0 - y2) < EPS
            && Mathf.Abs(y0 - y3) < EPS;
    }

    /// <summary>
    /// Vráti true, ak je face tile [tileX, tileZ] ŠIKMÝ – čistá RAMPA: dve
    /// susedné vertexy (jedna hrana) sú na jednej Y a protiľahlá hrana (druhé
    /// dva susedné vertexy) je na inej Y. Sklon teda vedie buď pozdĺž osi X,
    /// alebo pozdĺž osi Z.
    ///
    /// Rohy tile [x,z] (poradie ako GetFaceVertices):
    ///   y0=(x,z), y1=(x,z+1), y2=(x+1,z+1), y3=(x+1,z).
    ///
    /// NIE JE rampa: rovina (vylúčená podmienkou „hrany v rôznej výške“) ani
    /// skrútený tile (napr. jeden roh hore) – tam neplatí ani jeden zo vzorov.
    ///
    /// Slúži ako guard pre rovné koľaje/cesty, ktoré sa smú stavať aj na rampe.
    /// </summary>
    public bool IsFaceRamp(int tileX, int tileZ)
    {
        if (TerrainManager.instance == null) return false;

        float y0 = FunctionalValueY(tileX, tileZ);     // (x,   z)
        float y1 = FunctionalValueY(tileX, tileZ + 1); // (x,   z+1)
        float y2 = FunctionalValueY(tileX + 1, tileZ + 1); // (x+1, z+1)
        float y3 = FunctionalValueY(tileX + 1, tileZ);     // (x+1, z)

        const float EPS = 0.0001f;

        // Sklon pozdĺž Z: hrana pri z je rovná (y0==y3), hrana pri z+1 je rovná
        // (y1==y2), a navzájom sú v rôznej výške.
        bool rampAlongZ = Mathf.Abs(y0 - y3) < EPS
                       && Mathf.Abs(y1 - y2) < EPS
                       && Mathf.Abs(y0 - y1) >= EPS;

        // Sklon pozdĺž X: hrana pri x je rovná (y0==y1), hrana pri x+1 je rovná
        // (y3==y2), a navzájom sú v rôznej výške.
        bool rampAlongX = Mathf.Abs(y0 - y1) < EPS
                       && Mathf.Abs(y3 - y2) < EPS
                       && Mathf.Abs(y0 - y3) >= EPS;

        return rampAlongZ || rampAlongX;
    }

    /// <summary>
    /// Vráti true, ak vrchol na grid pozícii [vertX, vertZ] je rohom aspoň
    /// jedného OBSADENÉHO tile (tileID != 0).
    ///
    /// Jeden vrchol je zdieľaný až 4 susednými tilmi:
    ///   (vertX-1, vertZ-1), (vertX, vertZ-1), (vertX-1, vertZ), (vertX, vertZ).
    /// GetTileByIndexAny ošetrí indexy mimo mriežky (vráti tileID 0).
    ///
    /// Slúži ako guard pre úpravu terénu (LevelUp/LevelDown): ak by sa
    /// upravovaný vrchol dotýkal obsadeného tile, operácia sa zablokuje –
    /// a to aj vtedy, keď hráč klikol „vedľa“ obsadeného tile, lebo vrchol
    /// patrí aj jeho face.
    /// </summary>
    public bool IsVertexOnOccupiedTile(int vertX, int vertZ)
    {
        for (int tx = vertX - 1; tx <= vertX; tx++)
            for (int tz = vertZ - 1; tz <= vertZ; tz++)
                if (GetTileByIndexAny(tx, tz).tileID != 0)
                    return true;

        return false;
    }

    /// <summary>
    /// Vráti súradnice [x, z] VŠETKÝCH staníc danej kategórie na mape.
    ///
    /// Stanica je dlaždica s tileID == 2. Železnice a cesty zdieľajú jeden
    /// tile grid, líšia sa len kategóriou (TileCategory) – preto sa pri
    /// vyhľadávaní filtruje aj podľa category:
    ///   • TileCategory.Rail → vlakové (Train) stanice,
    ///   • TileCategory.Road → vozidlové (Vehicle) stanice.
    ///
    /// Metóda jednorázovo prejde celý tileGrid (GRID_SIZE × GRID_SIZE) a
    /// vyzbiera zhodné dlaždice. tileGrid ostáva privátny – navonok dávame
    /// len read-only zoznam súradníc, takže volajúci nemôže poškodiť mapu.
    ///
    /// Slúži pre informačné UI (StatusStationsMenuUI).
    /// </summary>
    /// <param name="category">Rail = Train stanice, Road = Vehicle stanice.</param>
    public List<Vector2Int> GetAllStationCoords(TileCategory category)
    {
        var result = new List<Vector2Int>();

        // tileGrid ešte nemusí byť inicializovaný (InitializeTileEngine sa
        // volá v Start()) – vtedy vrátime prázdny zoznam.
        if (tileGrid == null)
            return result;

        for (int x = 0; x < GRID_SIZE; x++)
        {
            for (int z = 0; z < GRID_SIZE; z++)
            {
                TileData td = tileGrid[x, z];
                if (td.tileID == 2 && td.category == category)
                    result.Add(new Vector2Int(x, z));
            }
        }

        return result;
    }

    /// <summary>
    /// Celkový počet staníc danej kategórie na mape. Pohodlný getter –
    /// vnútorne využíva GetAllStationCoords (počet = Count zoznamu).
    /// </summary>
    /// <param name="category">Rail = Train stanice, Road = Vehicle stanice.</param>
    public int GetStationCount(TileCategory category)
    {
        return GetAllStationCoords(category).Count;
    }

    bool IsValidIndex(Vector2Int index)
    {
        return index.x >= 0 && index.x < GRID_SIZE &&
               index.y >= 0 && index.y < GRID_SIZE;
    }

    void UpdateTileMap(int x, int z)
    {
        TileData data = tileGrid[x, z];

        if (tileMap[x, z] != null)
        {
            Destroy(tileMap[x, z]);
            tileMap[x, z] = null;
        }

        if (data.tileID == 0)
            return;

        GameObject go = new GameObject($"Tile_{x}_{z}");
        tileMap[x, z] = go;

        MeshFilter mf = go.AddComponent<MeshFilter>();
        MeshRenderer mr = go.AddComponent<MeshRenderer>();

        Mesh mesh = CreateTileMesh(x, z);
        mf.mesh = mesh;

        Material mat = new Material(snapFaceMatTex);

        // Výber textúry podľa kategórie (RAIL vs ROAD vs FACTORY) a tileID
        // (1 = trať/cesta, 2 = stanica, 3 = depo, 4 = továreň, 5 = spracovateľská)
        if (data.category == TileCategory.Road)
        {
            if (data.tileID == 1)
                mat.mainTexture = snapFaceTex_Road;
            else if (data.tileID == 2)
                mat.mainTexture = snapFaceTex_RoadStation;
            else if (data.tileID == 3)
                mat.mainTexture = snapFaceTex_RoadDepot;
        }
        else if (data.category == TileCategory.Factory)
        {
            if (data.tileID == 4)
                mat.mainTexture = snapFaceTex_Factory;
            else if (data.tileID == 5)
                mat.mainTexture = snapFaceTex_Processing;
        }
        else // Rail (alebo nezadefinované – default RAIL pre spätnú kompatibilitu)
        {
            if (data.tileID == 1)
                mat.mainTexture = snapFaceTex_Rail;
            else if (data.tileID == 2)
                mat.mainTexture = snapFaceTex_RailStation;
            else if (data.tileID == 3)
                mat.mainTexture = snapFaceTex_RailDepot;
        }

        mr.material = mat;
    }



    Mesh CreateTileMesh(int x, int z)
    {
        Vector3[] verts = new Vector3[4];

        float dy = 0.01f;

        verts[0] = new Vector3(x, FunctionalValueY(x, z) + dy, z);
        verts[1] = new Vector3(x, FunctionalValueY(x, z + 1) + dy, z + 1);
        verts[2] = new Vector3(x + 1, FunctionalValueY(x + 1, z + 1) + dy, z + 1);
        verts[3] = new Vector3(x + 1, FunctionalValueY(x + 1, z) + dy, z);

        Mesh mesh = new Mesh();
        mesh.vertices = verts;
        mesh.triangles = new int[] { 0, 1, 2, 2, 3, 0 };
        mesh.uv = new Vector2[]
        {
        new Vector2(0,0),
        new Vector2(1,0),
        new Vector2(1,1),
        new Vector2(0,1)
        };

        mesh.RecalculateNormals();
        return mesh;
    }




    // =========================
    // LOAD / SAVE
    // =========================
    //
    // KOMPLETNÁ uložená hra (binárne, VERZIOVANÉ). Jeden súbor SaveData.dat
    // drží všetko, čo sa NEDÁ deterministicky znovu vyrobiť. Veci, ktoré sa
    // dajú dopočítať z uloženého stavu, sa zámerne NEukladajú a po načítaní sa
    // vygenerujú:
    //
    //   ── UKLADÁ SA (zdroj pravdy, hráč to ovplyvnil) ─────────────────────
    //     • terén  – reálne Y výšky všetkých vrcholov (coordsF), lebo hráč ich
    //                upravuje (TerrainVertexLevel). Náhodný seed by neobnovil
    //                ručné úpravy, preto ukladáme priamo výšky.
    //     • tile grid – tileID + stateID + category každého tilu (256×256).
    //     • konto (GameEconomy.Balance, vrátane mínusu),
    //     • herný čas (GameClock: Year/Month/Day/TotalDaysElapsed),
    //     • cenový násobiteľ (ResourcePricing.Multiplier),
    //     • ročná účtovná kniha (BudgetSystem – akumulátor prebiehajúceho roka),
    //     • TOVÁRNE (FactoryRegistry) – typ, footprint, rotácia + PREMENLIVÝ
    //       stav: množstvá/kapacity slotov, mzda, mzdový level, occupancy flag,
    //       progres výstavby,
    //     • VLAKY (TrainSystem) a VOZIDLÁ (VehicleSystem) viazané na depá –
    //       typ súpravy/vozidla, vek, náklad, priradené stanice, beh/stop.
    //
    //   ── NEUKLADÁ SA (dopočíta sa pri LoadGame) ──────────────────────────
    //     • mesh terénu (prekreslí sa z výšok),
    //     • textúry/GameObjekty tilov (UpdateTileMap z tile gridu),
    //     • priradenie tovární staniciam – 9×9 catchment (RescanAll),
    //     • konkrétne trasy/sub-tile pozícia vlakov a vozidiel (A* sa prepočíta;
    //       po načítaní stoja v depe a ak bežali, znovu sa rozbehnú).
    //
    // Poradie blokov je ZHODNÉ pri zápise aj čítaní. Tile engine je orchestrátor:
    // terén/tiles/továrne/skalárne stavy rieši sám, serializáciu vlakov,
    // vozidiel a knihy delegujе na príslušné systémy (každý si vlastný stav
    // serializuje sám – pozri WriteSave/ReadSave v TrainSystem/VehicleSystem/
    // BudgetSystem v companion patchi).

    private const int SAVE_MAGIC = 0x494E4458;   // "INDX"
    private const int SAVE_VERSION = 2;

    public void SaveGame()
    {
        string path = Application.persistentDataPath + "/SaveData.dat";

        using (BinaryWriter bw = new BinaryWriter(File.Open(path, FileMode.Create)))
        {
            // ── Hlavička ──
            bw.Write(SAVE_MAGIC);
            bw.Write(SAVE_VERSION);

            // ── 1) TERÉN (reálne Y výšky vrcholov) ──
            WriteTerrain(bw);

            // ── 2) TILE GRID ──
            for (int x = 0; x < GRID_SIZE; x++)
            {
                for (int z = 0; z < GRID_SIZE; z++)
                {
                    bw.Write(tileGrid[x, z].tileID);
                    bw.Write(tileGrid[x, z].stateID);
                    bw.Write((int)tileGrid[x, z].category);
                    // connections sa neukladá – odvodí sa pri Load zo stateID a category.
                }
            }

            // ── 3) KONTO / ČAS / CENOVÝ NÁSOBITEĽ ──
            bw.Write(GameEconomy.instance != null ? GameEconomy.instance.Balance : 0L);

            var clock = GameClock.instance;
            bw.Write(clock != null ? clock.Year : 1950);
            bw.Write(clock != null ? clock.Month : 1);
            bw.Write(clock != null ? clock.Day : 1);
            bw.Write(clock != null ? clock.TotalDaysElapsed : 0L);

            bw.Write(ResourcePricing.Multiplier);

            // ── 4) ROČNÁ ÚČTOVNÁ KNIHA ──
            if (BudgetSystem.instance != null) BudgetSystem.instance.WriteSave(bw);
            else WriteEmptyBudget(bw);

            // ── 5) TOVÁRNE ──
            WriteFactories(bw);

            // ── 6) VLAKY a VOZIDLÁ ──
            if (TrainSystem.instance != null) TrainSystem.instance.WriteSave(bw);
            else bw.Write(0);
            if (VehicleSystem.instance != null) VehicleSystem.instance.WriteSave(bw);
            else bw.Write(0);
        }

        Debug.Log("[IndicatrixAPI] Hra uložená do " + path);
    }

    public void LoadGame()
    {
        string path = Application.persistentDataPath + "/SaveData.dat";
        if (!File.Exists(path)) return;

        using (BinaryReader br = new BinaryReader(File.Open(path, FileMode.Open)))
        {
            // ── Hlavička ──
            int magic = br.ReadInt32();
            int version = br.ReadInt32();
            if (magic != SAVE_MAGIC)
            {
                Debug.LogError("[IndicatrixAPI] SaveData.dat má neznámy formát (magic) – " +
                               "načítanie zrušené.");
                return;
            }
            if (version != SAVE_VERSION)
            {
                Debug.LogWarning($"[IndicatrixAPI] SaveData.dat má verziu {version} (očakávaná " +
                                 $"{SAVE_VERSION}). Pokúsim sa načítať, no formát sa mohol zmeniť.");
            }

            // ── 1) TERÉN ──
            ReadTerrain(br);

            // ── 2) TILE GRID (+ prekreslenie meshu každého tilu) ──
            for (int x = 0; x < GRID_SIZE; x++)
            {
                for (int z = 0; z < GRID_SIZE; z++)
                {
                    int tileID = br.ReadInt32();
                    int stateID = br.ReadInt32();
                    TileCategory category = (TileCategory)br.ReadInt32();

                    DirectionMask conns = RebuildConnections(tileID, stateID, ref category);
                    tileGrid[x, z] = new TileData(tileID, stateID, conns, category);
                    UpdateTileMap(x, z);
                }
            }

            // ── 3) KONTO / ČAS / NÁSOBITEĽ ──
            long balance = br.ReadInt64();
            int year = br.ReadInt32();
            int month = br.ReadInt32();
            int day = br.ReadInt32();
            long totalDays = br.ReadInt64();
            int multiplier = br.ReadInt32();

            if (GameEconomy.instance != null) GameEconomy.instance.SetBalanceSigned(balance);
            if (GameClock.instance != null) GameClock.instance.LoadDate(year, month, day, totalDays);
            ResourcePricing.Multiplier = multiplier < 1 ? 1 : multiplier;

            // EconomySystem si pri štarte zapamätal lastMonth/lastYear z PÔVODNÉHO
            // (defaultného) dátumu. Po prepísaní hodín ich treba zladiť, inak by
            // najbližší denný tik mohol omylom zúčtovať "nový mesiac/rok" navyše.
            if (EconomySystem.instance != null) EconomySystem.instance.ResyncToClock();

            // ── 4) ROČNÁ KNIHA ──
            if (BudgetSystem.instance != null) BudgetSystem.instance.ReadSave(br);
            else SkipBudget(br);

            // ── 5) TOVÁRNE (vyprázdni register, potom obnov) ──
            ReadFactories(br);

            // ── 6) PRIRADENIE TOVÁRNÍ STANICIAM – dopočítané, NEukladá sa ──
            //   Tile grid + továrne sú už načítané, takže scan má z čoho zbierať.
            RailStationRegistry.RescanAll();
            RoadStationRegistry.RescanAll();

            // ── 7) VLAKY a VOZIDLÁ ──
            if (TrainSystem.instance != null) TrainSystem.instance.ReadSave(br);
            else SkipCollection(br);
            if (VehicleSystem.instance != null) VehicleSystem.instance.ReadSave(br);
            else SkipCollection(br);
        }

        Debug.Log("[IndicatrixAPI] Hra načítaná z " + path);
    }

    // ------------------------------------------------------------------
    // POMOCNÉ – TERÉN
    // ------------------------------------------------------------------
    //
    // Ukladajú sa reálne Y výšky vrcholov (coordsF). x,z sú deterministicky
    // dané indexom (CreateMap), preto ich netreba ukladať. Po načítaní výšok
    // sa dopočíta coordsI (ConvertCoordsFI(true)) a prekreslia sa meshe
    // elementov terénu.

    private void WriteTerrain(BinaryWriter bw)
    {
        var tm = TerrainManager.instance;
        if (tm == null || tm.coordsF == null)
        {
            bw.Write(0);
            return;
        }

        bw.Write(tm.coordsF.Length);
        for (int i = 0; i < tm.coordsF.Length; i++)
            bw.Write(tm.coordsF[i].y);
    }

    private void ReadTerrain(BinaryReader br)
    {
        int count = br.ReadInt32();

        var tm = TerrainManager.instance;
        if (tm == null || tm.coordsF == null)
        {
            // Preskoč dáta (4 bajty na float), nech ostane stream zarovnaný.
            for (int i = 0; i < count; i++) br.ReadSingle();
            Debug.LogWarning("[IndicatrixAPI] TerrainManager nie je pripravený – výšky preskočené.");
            return;
        }

        if (count != tm.coordsF.Length)
        {
            Debug.LogWarning($"[IndicatrixAPI] Veľkosť terénu v save ({count}) sa nezhoduje s " +
                             $"aktuálnou ({tm.coordsF.Length}). Načítam min. spoločnú časť.");
        }

        int n = Mathf.Min(count, tm.coordsF.Length);
        for (int i = 0; i < n; i++)
        {
            float y = br.ReadSingle();
            tm.coordsF[i].y = y;
        }
        // Zvyšok (ak bol save väčší) dočítaj, nech je stream zarovnaný.
        for (int i = n; i < count; i++) br.ReadSingle();

        // Dopočítaj celočíselné úrovne z výšok a prekresli meshe.
        tm.ConvertCoordsFI(true);
        if (tm.terrainElements != null)
        {
            foreach (var e in tm.terrainElements)
                if (e != null) e.Rebuild();
        }
    }

    // ------------------------------------------------------------------
    // POMOCNÉ – TILE CONNECTIONS (odvodenie pri Load, ako v pôvodnej verzii)
    // ------------------------------------------------------------------
    private DirectionMask RebuildConnections(int tileID, int stateID, ref TileCategory category)
    {
        if (tileID == 0)
            return DirectionMask.None;

        if (category == TileCategory.Road)
            return GetConnectionsForMode((GameManager.RoadConstructionMode)stateID);

        if (category == TileCategory.Factory)
            return GetConnectionsForMode((GameManager.FactoryConstructionMode)stateID); // None

        // Rail (alebo None pri starších záznamoch – dorovnáme na Rail).
        if (category == TileCategory.None) category = TileCategory.Rail;
        return GetConnectionsForMode((GameManager.RailConstructionMode)stateID);
    }

    // ------------------------------------------------------------------
    // POMOCNÉ – TOVÁRNE
    // ------------------------------------------------------------------
    //
    // Ukladá sa typ (FactoryConstructionMode), footprint (origin + rozmery),
    // rotácia a PREMENLIVÝ stav. Pri Load sa register vyprázdni a každá továreň
    // sa znovu zaregistruje cez FactoryRegistry.Register (rovnaká cesta ako pri
    // ručnom položení), následne sa obnovia premenlivé hodnoty.

    private void WriteFactories(BinaryWriter bw)
    {
        var all = FactoryRegistry.All;
        bw.Write(all.Count);

        foreach (var f in all)
        {
            bw.Write((int)f.Definition.Mode);
            bw.Write(f.OriginX);
            bw.Write(f.OriginZ);
            bw.Write(f.Width);
            bw.Write(f.Depth);
            bw.Write((int)f.Rotation);

            // Premenlivý stav.
            bw.Write(f.EmployeeSalary);
            bw.Write(f.LevelSalary);
            bw.Write(f.OccupancyFlag);
            bw.Write(f.BuildElapsed);     // progres výstavby (sekundy)

            // Sloty (amount + capacity – kapacitu hráč mohol editovať).
            WriteSlots(bw, f.Load);
            WriteSlots(bw, f.UnLoad);
        }
    }

    private void ReadFactories(BinaryReader br)
    {
        FactoryRegistry.Clear();

        int count = br.ReadInt32();
        for (int k = 0; k < count; k++)
        {
            var mode = (GameManager.FactoryConstructionMode)br.ReadInt32();
            int originX = br.ReadInt32();
            int originZ = br.ReadInt32();
            int width = br.ReadInt32();
            int depth = br.ReadInt32();
            var rotation = (FactoryRotation)br.ReadInt32();

            int employeeSalary = br.ReadInt32();
            short levelSalary = br.ReadInt16();
            bool occupancy = br.ReadBoolean();
            float buildElapsed = br.ReadSingle();

            // Sloty zo save (do dočasných zoznamov, aplikujeme po Register).
            var savedLoad = ReadSlots(br);
            var savedUnLoad = ReadSlots(br);

            FactoryDefinition def = FactoryDatabase.GetDefinition(mode);
            if (def == null)
            {
                Debug.LogWarning($"[IndicatrixAPI] Neznáma továreň '{mode}' v save – preskočená.");
                continue;
            }

            var inst = FactoryRegistry.Register(def, originX, originZ, width, depth, rotation);
            if (inst == null) continue;

            inst.EmployeeSalary = employeeSalary;
            inst.LevelSalary = levelSalary;
            inst.OccupancyFlag = occupancy;

            // Obnov množstvá/kapacity slotov (poradie je deterministické z definície).
            ApplySavedSlots(inst.Load, savedLoad);
            ApplySavedSlots(inst.UnLoad, savedUnLoad);

            // Obnov progres výstavby. Register cez konštruktor začína na 0 a
            // (ak má def BuildingTime>0) v stave "vo výstavbe". AdvanceConstruction
            // dotiahne elapsed na uloženú hodnotu; pri dokončení sa flag zhodí sám.
            inst.AdvanceConstruction(buildElapsed);
        }
    }

    private static void WriteSlots(BinaryWriter bw, System.Collections.Generic.List<ResourceSlot> slots)
    {
        bw.Write(slots.Count);
        foreach (var s in slots)
        {
            bw.Write((int)s.type);
            bw.Write(s.amount);
            bw.Write(s.capacity);
        }
    }

    private struct SavedSlot { public int type; public int amount; public int capacity; }

    private static System.Collections.Generic.List<SavedSlot> ReadSlots(BinaryReader br)
    {
        int n = br.ReadInt32();
        var list = new System.Collections.Generic.List<SavedSlot>(n);
        for (int i = 0; i < n; i++)
            list.Add(new SavedSlot { type = br.ReadInt32(), amount = br.ReadInt32(), capacity = br.ReadInt32() });
        return list;
    }

    private static void ApplySavedSlots(System.Collections.Generic.List<ResourceSlot> target,
                                        System.Collections.Generic.List<SavedSlot> saved)
    {
        // Spárуj podľa typu (robustnejšie než podľa indexu, keby sa definícia
        // časom rozšírila). amount aj capacity prepíšeme zo save.
        foreach (var sv in saved)
        {
            var rt = (ResourceType)sv.type;
            foreach (var slot in target)
            {
                if (slot.type == rt)
                {
                    slot.capacity = Mathf.Max(0, sv.capacity);
                    slot.amount = Mathf.Clamp(sv.amount, 0, slot.capacity);
                    break;
                }
            }
        }
    }

    // ------------------------------------------------------------------
    // POMOCNÉ – PRÁZDNE / PRESKOČENIE BLOKOV (keď systém v scéne chýba)
    // ------------------------------------------------------------------

    private void WriteEmptyBudget(BinaryWriter bw)
    {
        // 10 položiek knihy (pozri BudgetSystem.WriteSave) – samé nuly.
        bw.Write(0L);  // Revenue
        bw.Write(0); bw.Write(0L);  // TrainsBought, TrainsCost
        bw.Write(0); bw.Write(0L);  // VehiclesBought, VehiclesCost
        bw.Write(0); bw.Write(0L);  // FactoriesBought, FactoriesCost
        bw.Write(0L);  // TrainOperating
        bw.Write(0L);  // VehicleOperating
        bw.Write(0L);  // EmployeeSalaries
    }

    private void SkipBudget(BinaryReader br)
    {
        br.ReadInt64();
        br.ReadInt32(); br.ReadInt64();
        br.ReadInt32(); br.ReadInt64();
        br.ReadInt32(); br.ReadInt64();
        br.ReadInt64();
        br.ReadInt64();
        br.ReadInt64();
    }

    // Vlaky/vozidlá serializujú prvý int = count, potom count záznamov. Ak
    // systém v scéne nie je, nevieme bezpečne preskočiť premenlivú dĺžku –
    // ošetríme len count==0 (čo zapisuje SaveGame, keď systém chýbal).
    private void SkipCollection(BinaryReader br)
    {
        int count = br.ReadInt32();
        if (count != 0)
            Debug.LogWarning("[IndicatrixAPI] Save obsahuje vlaky/vozidlá, ale systém v scéne " +
                             "chýba – ich blok sa nedá preskočiť. Stream je odteraz nezarovnaný.");
    }

    // =========================
    // ORIGINAL SNAP FUNCTIONS
    // =========================

    public Vector3 SnapVertex(Vector3 hitPoint)
    {
        HideSnapLines();
        ShowSnapSphere();

        int snapX = 0, snapZ = 0;
        float snapY = 0;

        int xx = (int)hitPoint.x; float x = hitPoint.x - xx;
        int zz = (int)hitPoint.z; float z = hitPoint.z - zz;

        if (x >= 0.0f && x <= 0.5f) { snapX = xx; }
        else snapX = xx + 1;

        if (z >= 0.0f && z <= 0.5f) { snapZ = zz; }
        else snapZ = zz + 1;

        snapY = FunctionalValueY(snapX, snapZ);

        snapSphere.transform.position = new Vector3(snapX, snapY, snapZ);

        return new Vector3(snapX, snapY, snapZ);
    }

    public Vector3 SnapLineFace(Vector3 hitPoint)
    {
        HideSnapSphere();
        ShowSnapLines();

        Vector3[] verts = GetFaceVertices(hitPoint);

        lineRenderer01.SetPosition(0, verts[0]);
        lineRenderer01.SetPosition(1, verts[1]);

        lineRenderer02.SetPosition(0, verts[1]);
        lineRenderer02.SetPosition(1, verts[2]);

        lineRenderer03.SetPosition(0, verts[2]);
        lineRenderer03.SetPosition(1, verts[3]);

        lineRenderer04.SetPosition(0, verts[3]);
        lineRenderer04.SetPosition(1, verts[0]);

        return GetCenterFace(hitPoint);
    }

    public Vector3 SnapMeshFace(Vector3 hitPoint)
    {
        return GetCenterFace(hitPoint);
    }

    /// <summary>
    /// SnapAreaFace – snap vizuál pre VIAC-TILE footprint (továrne).
    ///
    /// Analógia k SnapLineFace, ale namiesto jednej 1×1 plochy zvýrazní
    /// obvod celého obdĺžnika N×M tilov, do ktorého sa továreň umiestni.
    /// Hit point určuje anchor tile; obdĺžnik sa dopočíta cez FactoryFootprint
    /// rovnako ako v SetTile (FACTORY verzia), takže náhľad presne zodpovedá
    /// tomu, čo sa po kliknutí položí.
    ///
    /// Používa tie isté 4 line renderery ako SnapLineFace – vykreslí nimi
    /// 4 strany veľkého obdĺžnika namiesto malého tilu.
    ///
    /// PARAMETER rotation: footprint sa zvýrazní UŽ otočený, takže náhľad
    /// presne zodpovedá tomu, čo SetTile s rovnakou rotáciou položí.
    /// Default = Deg0.
    /// </summary>
    public Vector3 SnapAreaFace(Vector3 hitPoint, GameManager.FactoryConstructionMode mode,
                                FactoryRotation rotation = FactoryRotation.Deg0)
    {
        HideSnapSphere();
        ShowSnapLines();

        Vector2Int anchor = SnapTileIndex(hitPoint);
        FactoryFootprint fp = GetFactoryFootprint(mode, rotation);

        // Ľavý-dolný roh footprintu (rovnaký výpočet ako v SetTile).
        int minX = anchor.x - fp.anchorX;
        int minZ = anchor.y - fp.anchorZ;
        int maxX = minX + fp.width;   // +width  → pravá hrana posledného tilu
        int maxZ = minZ + fp.depth;   // +depth  → horná hrana posledného tilu

        float dy = 0.02f;

        // 4 rohy veľkého obdĺžnika (proti smeru hod. ručičiek).
        Vector3 c0 = new Vector3(minX, FunctionalValueY(minX, minZ) + dy, minZ);
        Vector3 c1 = new Vector3(minX, FunctionalValueY(minX, maxZ) + dy, maxZ);
        Vector3 c2 = new Vector3(maxX, FunctionalValueY(maxX, maxZ) + dy, maxZ);
        Vector3 c3 = new Vector3(maxX, FunctionalValueY(maxX, minZ) + dy, minZ);

        lineRenderer01.SetPosition(0, c0);
        lineRenderer01.SetPosition(1, c1);

        lineRenderer02.SetPosition(0, c1);
        lineRenderer02.SetPosition(1, c2);

        lineRenderer03.SetPosition(0, c2);
        lineRenderer03.SetPosition(1, c3);

        lineRenderer04.SetPosition(0, c3);
        lineRenderer04.SetPosition(1, c0);

        // Geometrický stred footprintu (užitočné pre prípadné umiestnenie
        // 3D modelu továrne neskôr).
        float cx = (minX + maxX) * 0.5f;
        float cz = (minZ + maxZ) * 0.5f;
        return new Vector3(cx, FunctionalValueY(anchor.x, anchor.y), cz);
    }

    Vector3[] GetFaceVertices(Vector3 hitPoint)
    {
        int xx = Mathf.FloorToInt(hitPoint.x);
        int zz = Mathf.FloorToInt(hitPoint.z);

        float dy = 0.01f;

        Vector3[] verts = new Vector3[4];

        verts[0] = new Vector3(xx, FunctionalValueY(xx, zz) + dy, zz);
        verts[1] = new Vector3(xx, FunctionalValueY(xx, zz + 1) + dy, zz + 1);
        verts[2] = new Vector3(xx + 1, FunctionalValueY(xx + 1, zz + 1) + dy, zz + 1);
        verts[3] = new Vector3(xx + 1, FunctionalValueY(xx + 1, zz) + dy, zz);

        return verts;
    }

    public Vector3 GetCenterFace(Vector3 hitPoint)
    {
        int xx = Mathf.FloorToInt(hitPoint.x);
        int zz = Mathf.FloorToInt(hitPoint.z);

        float dx = xx + 0.5f;
        float dz = zz + 0.5f;
        float dy = FunctionalValueY(xx, zz);

        return new Vector3(dx, dy, dz);
    }

    float FunctionalValueY(int snapX, int snapZ)
    {
        int width = TerrainManager.instance.terrainWidth + 1;

        int index = snapZ * width + snapX;

        return TerrainManager.instance.coordsF[index].y;
    }


}

TrainStock.cs

using System;
using System.Collections.Generic;

/// <summary>
/// TrainStock.cs
/// ─────────────────────────────────────────────────────────────────────────
/// VOZOVÝ PARK – čisté dátové štruktúry (žiadne MonoBehaviour, žiadne Unity API).
///
/// Tento súbor je zámerne oddelený od TrainSystem.cs:
///   • TrainSystem.cs = pohybová logika, A* pathfinding, vizuál (kvádre).
///   • TrainStock.cs  = "čo je vlak / vagón" – iba dáta a katalógy.
///
/// ─────────────────────────────────────────────────────────────────────────
/// ROZDELENIE: "Spec" (predloha) vs. "Instance" (inštancia v hre)
///
///   *Spec     – nemenná predloha typu (katalógová karta). Spoločná pre všetky
///               vlaky/vagóny toho istého typu. Napr. TrainSpec "Iron Dragon"
///               má Cost, Speed, Power... – tieto hodnoty sú rovnaké pre každý
///               vyrobený kus.
///
///   *Instance – konkrétny kus v hre, vytvorený v depe. Drží referenciu na
///               svoj Spec + premenlivý stav daného kusu (napr. Age vlaku,
///               CurrentCapacity vagónu – tie sa líšia kus od kusu a menia
///               sa počas hry).
///
/// Vďaka tomu sa nemenné parametre (Cost, Speed...) NEDUPLIKUJÚ do každého
/// vlaku – sú raz v katalógu. UI okno s detailmi vlaku si ich len prečíta
/// cez Instance.Spec.
/// ─────────────────────────────────────────────────────────────────────────
///
/// POZNÁMKA K TYPOM (revízia):
///   Polia Weight / Power / YearOfManufacture (TrainSpec) a Weight (WagonSpec)
///   sú modelované ako int (číselné hodnoty), nie ako string s jednotkou.
///   Pôvodne to boli stringy ("55t", "720hp", "1984", "5t"), ale nikde mimo
///   tohto súboru sa nečítali a zadané dáta vlakov/vagónov sú číselné. Číselný
///   typ je tak vhodnejší (dá sa s ním počítať – hmotnosť súpravy, výkon a pod.)
///   a presne zodpovedá zadaným parametrom.
/// ─────────────────────────────────────────────────────────────────────────
/// </summary>

namespace Game.TrainStock
{
    // =========================================================================
    // TYP VAGÓNU
    //
    // Zadanie hovorí o "hash table (string, int)" = [textové označenie, číselné
    // označenie]. To je ale len JEDNA dvojica hodnôt pre jeden typ vagónu –
    // nie kolekcia. Použiť Dictionary/Hashtable na uloženie jednej dvojice je
    // zbytočné (alokácia, žiadna typová bezpečnosť, neprehľadné).
    //
    // Preto je typ vagónu modelovaný ako malý nemenný struct WagonType
    // (Name + Id). "Hash table" charakter – teda vyhľadanie typu podľa názvu
    // alebo podľa čísla – zabezpečuje statický register WagonCatalog, ktorý
    // skutočné slovníky (ByName, ById) drží centrálne na jednom mieste.
    //
    // Ak by si predsa len chcel priamo slovník na konkrétnom vagóne, dá sa
    // získať jednoriadkovo: WagonType.AsPair() vráti KeyValuePair<string,int>.
    //
    // DÔLEŽITÉ – ZHODA ID S ResourceType (FactorySystem.cs):
    //   Číselné Id typu vagónu je ZÁMERNE rovnaké ako (int)ResourceType:
    //     "Coal Truck"        #1  → ResourceType.Coal                = 1
    //     "Wood Truck"        #2  → ResourceType.Wood                = 2
    //     "Iron Ore Truck"    #3  → ResourceType.IronOre             = 3
    //     ...
    //     "Electronics Truck" #16 → ResourceType.ElectronicsProducts = 16
    //   Vďaka tomu TrainTradeSystem.WagonResource() namapuje vagón na surovinu
    //   bez extra poľa (Id == (int)ResourceType). Pri pridávaní nového vagónu
    //   teda Id MUSÍ zodpovedať príslušnej hodnote v enum ResourceType.
    // =========================================================================

    /// <summary>
    /// Typ vagónu – nemenná dvojica [textové označenie, číselné označenie].
    /// Napr. ("Coal Truck", 1), ("Wood Truck", 2).
    /// </summary>
    [Serializable]
    public readonly struct WagonType : IEquatable<WagonType>
    {
        /// <summary>Textové označenie typu vagónu, napr. "Coal Truck".</summary>
        public readonly string Name;

        /// <summary>Číselné označenie typu vagónu (== (int)ResourceType), napr. 1.</summary>
        public readonly int Id;

        public WagonType(string name, int id)
        {
            Name = name;
            Id = id;
        }

        /// <summary>
        /// Vráti typ vagónu ako dvojicu kľúč–hodnota (string, int).
        /// Pohodlné, ak niektorá časť kódu očakáva "hash table" reprezentáciu.
        /// </summary>
        public KeyValuePair<string, int> AsPair() => new KeyValuePair<string, int>(Name, Id);

        public bool Equals(WagonType other) => Id == other.Id && Name == other.Name;
        public override bool Equals(object obj) => obj is WagonType w && Equals(w);
        public override int GetHashCode() => Id;
        public override string ToString() => $"{Name} (#{Id})";
    }

    // =========================================================================
    // PREDLOHA VAGÓNU – WagonSpec
    // =========================================================================

    /// <summary>
    /// Predloha (katalógová karta) jedného typu vagónu.
    /// Nemenné parametre spoločné pre všetky kusy daného typu.
    /// </summary>
    [Serializable]
    public sealed class WagonSpec
    {
        /// <summary>
        /// Názov vagónu, napr. "Coal Truck".
        /// Pozn.: názov sa dá odvodiť aj z Type.Name. Necháme ho ako samostatné
        /// pole, lebo UI prvok WagonTypeDropdown pracuje s názvami priamo a je
        /// pohodlnejšie mať jeden zdroj pre zobrazenie. Drží sa konzistentne
        /// s Type.Name (viď WagonCatalog, kde sa oba nastavujú spolu).
        /// </summary>
        public string Name;

        /// <summary>Typ vagónu – [textové označenie, číselné označenie].</summary>
        public WagonType Type;

        /// <summary>Cena vagónu.</summary>
        public int Cost;

        /// <summary>Hmotnosť vagónu (číselne, napr. 5).</summary>
        public int Weight;

        /// <summary>Maximálna kapacita vagónu.</summary>
        public int MaximumCapacity;

        public WagonSpec(string name, WagonType type, int cost, int weight, int maximumCapacity)
        {
            Name = name;
            Type = type;
            Cost = cost;
            Weight = weight;
            MaximumCapacity = maximumCapacity;
        }
    }

    // =========================================================================
    // INŠTANCIA VAGÓNU – WagonInstance
    // =========================================================================

    /// <summary>
    /// Konkrétny vagón existujúci v hre. Drží referenciu na svoj WagonSpec
    /// (nemenné parametre) + premenlivý stav (aktuálna kapacita).
    /// </summary>
    [Serializable]
    public sealed class WagonInstance
    {
        /// <summary>Predloha typu tohto vagónu (nemenné parametre).</summary>
        public readonly WagonSpec Spec;

        /// <summary>
        /// Aktuálna kapacita (naložený náklad). Pri vytvorení 0.
        /// Mení sa počas hry pri nakladaní/vykladaní (viď TrainTradeSystem).
        /// </summary>
        public int CurrentCapacity;

        public WagonInstance(WagonSpec spec)
        {
            Spec = spec ?? throw new ArgumentNullException(nameof(spec));
            CurrentCapacity = 0;
        }

        // ----- Pohodlné skratky na nemenné parametre (čítané z UI okna) -----
        public string Name => Spec.Name;
        public WagonType Type => Spec.Type;
        public int Cost => Spec.Cost;
        public int Weight => Spec.Weight;
        public int MaximumCapacity => Spec.MaximumCapacity;
    }

    // =========================================================================
    // PREDLOHA VLAKU / LOKOMOTÍVY – TrainSpec
    // =========================================================================

    /// <summary>
    /// Predloha (katalógová karta) jedného typu vlaku (lokomotívy).
    /// Nemenné parametre spoločné pre všetky kusy daného typu.
    /// </summary>
    [Serializable]
    public sealed class TrainSpec
    {
        /// <summary>Názov vlaku, napr. "Iron Dragon".</summary>
        public string Name;

        /// <summary>Cena vlaku.</summary>
        public int Cost;

        /// <summary>Prevádzkové náklady.</summary>
        public int OperatingCosts;

        /// <summary>Rýchlosť vlaku.</summary>
        public int Speed;

        /// <summary>Hmotnosť vlaku (číselne, napr. 150).</summary>
        public int Weight;

        /// <summary>Výkon vlaku (číselne, napr. 60).</summary>
        public int Power;

        /// <summary>Životnosť vlaku.</summary>
        public int ServiceLife;

        /// <summary>Servisný interval.</summary>
        public int ServicingInterval;

        /// <summary>Rok výroby (číselne, napr. 1930).</summary>
        public int YearOfManufacture;

        public TrainSpec(string name, int cost, int operatingCosts, int speed,
                         int weight, int power, int serviceLife,
                         int servicingInterval, int yearOfManufacture)
        {
            Name = name;
            Cost = cost;
            OperatingCosts = operatingCosts;
            Speed = speed;
            Weight = weight;
            Power = power;
            ServiceLife = serviceLife;
            ServicingInterval = servicingInterval;
            YearOfManufacture = yearOfManufacture;
        }
    }

    // =========================================================================
    // INŠTANCIA VLAKU – TrainInstance
    //
    // Pozn.: TrainInstance je čisto dátový popis vlakovej súpravy
    // (lokomotíva + vagóny). Pohybový/pathfinding stav (PathData, stanice,
    // isRunning...) zostáva v TrainSystem.TrainData. TrainData drží referenciu
    // na TrainInstance – viď integráciu nižšie v poznámke pre TrainSystem.cs.
    // =========================================================================

    /// <summary>
    /// Konkrétny vlak existujúci v hre. Drží referenciu na svoj TrainSpec
    /// (nemenné parametre) + premenlivý stav (vek) + zoznam vagónov.
    /// </summary>
    [Serializable]
    public sealed class TrainInstance
    {
        /// <summary>Predloha typu tohto vlaku (nemenné parametre).</summary>
        public readonly TrainSpec Spec;

        /// <summary>
        /// Aktuálny vek vlaku. Pri vytvorení 0.
        /// Mení sa počas hry (pribúdaním herného času).
        /// </summary>
        public int Age;

        /// <summary>Vagóny pripojené k tomuto vlaku (poradie = poradie v súprave).</summary>
        public readonly List<WagonInstance> Wagons = new List<WagonInstance>();

        public TrainInstance(TrainSpec spec)
        {
            Spec = spec ?? throw new ArgumentNullException(nameof(spec));
            Age = 0;
        }

        // ----- Pohodlné skratky na nemenné parametre (čítané z UI okna) -----
        public string Name => Spec.Name;
        public int Cost => Spec.Cost;
        public int OperatingCosts => Spec.OperatingCosts;
        public int Speed => Spec.Speed;
        public int Weight => Spec.Weight;
        public int Power => Spec.Power;
        public int ServiceLife => Spec.ServiceLife;
        public int ServicingInterval => Spec.ServicingInterval;
        public int YearOfManufacture => Spec.YearOfManufacture;
    }

    // =========================================================================
    // KATALÓG VLAKOV – TrainCatalog
    //
    // Centrálne miesto, kde sú definované všetky typy vlakov. Slúži ako zdroj
    // pre TrainTypeDropdown (názvy) aj pre TrainSystem (vytvorenie inštancie).
    // Poradie v zozname = poradie položiek v TrainTypeDropdown (index 1:1).
    // =========================================================================

    public static class TrainCatalog
    {
        /// <summary>
        /// Všetky dostupné typy vlakov, v poradí zhodnom s TrainTypeDropdown.
        /// Tri lokomotívy: "Iron Dragon", "Desert Runner", "Thunderbolt".
        /// </summary>
        public static readonly IReadOnlyList<TrainSpec> All = new List<TrainSpec>
        {
            // Vlak 1
            new TrainSpec(
                name:              "Iron Dragon",
                cost:              15000,
                operatingCosts:    50,
                speed:             60,
                weight:            150,
                power:             60,
                serviceLife:       8,
                servicingInterval: 150,
                yearOfManufacture: 1930
            ),
            // Vlak 2
            new TrainSpec(
                name:              "Desert Runner",
                cost:              23000,
                operatingCosts:    70,
                speed:             100,
                weight:            200,
                power:             90,
                serviceLife:       15,
                servicingInterval: 200,
                yearOfManufacture: 1970
            ),
            // Vlak 3
            new TrainSpec(
                name:              "Thunderbolt",
                cost:              33000,
                operatingCosts:    80,
                speed:             150,
                weight:            200,
                power:             80,
                serviceLife:       20,
                servicingInterval: 180,
                yearOfManufacture: 2000
            ),
        };

        /// <summary>Názvy typov vlakov – priamo použiteľné pre TrainTypeDropdown.</summary>
        public static string[] Names()
        {
            var names = new string[All.Count];
            for (int i = 0; i < All.Count; i++) names[i] = All[i].Name;
            return names;
        }

        /// <summary>Vráti TrainSpec podľa indexu v dropdowne (bezpečné voči rozsahu).</summary>
        public static TrainSpec ByIndex(int index)
        {
            if (index < 0 || index >= All.Count) return null;
            return All[index];
        }

        /// <summary>Vráti TrainSpec podľa názvu, alebo null.</summary>
        public static TrainSpec ByName(string name)
        {
            for (int i = 0; i < All.Count; i++)
                if (All[i].Name == name) return All[i];
            return null;
        }
    }

    // =========================================================================
    // KATALÓG VAGÓNOV – WagonCatalog
    //
    // Centrálne miesto, kde sú definované všetky typy vagónov. Drží aj
    // "hash table" lookupy (ByName, ById) – to je miesto, kde má slovník
    // skutočný zmysel (kolekcia viacerých typov).
    //
    // Id každého vagónu sa zhoduje s (int)ResourceType (FactorySystem.cs),
    // takže keď vlak dorazí do stanice, TrainTradeSystem.WagonResource()
    // automaticky rozpozná surovinu pre ktorýkoľvek z týchto 16 typov.
    // =========================================================================

    public static class WagonCatalog
    {
        /// <summary>
        /// Všetky dostupné typy vagónov, v poradí zhodnom s WagonTypeDropdown.
        /// 16 typov – Id zodpovedá ResourceType (1 = Coal ... 16 = Electronics).
        /// </summary>
        public static readonly IReadOnlyList<WagonSpec> All = new List<WagonSpec>
        {
            // Vagón 1 – už existoval
            new WagonSpec(
                name:            "Coal Truck",
                type:            new WagonType("Coal Truck", 1),
                cost:            35,
                weight:          5,
                maximumCapacity: 5
            ),
            // Vagón 2 – už existoval
            new WagonSpec(
                name:            "Wood Truck",
                type:            new WagonType("Wood Truck", 2),
                cost:            45,
                weight:          7,
                maximumCapacity: 5
            ),
            // Vagón 3
            new WagonSpec(
                name:            "Iron Ore Truck",
                type:            new WagonType("Iron Ore Truck", 3),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 4
            new WagonSpec(
                name:            "Gold Truck",
                type:            new WagonType("Gold Truck", 4),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 5
            new WagonSpec(
                name:            "Silver Truck",
                type:            new WagonType("Silver Truck", 5),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 6
            new WagonSpec(
                name:            "Livestock Truck",
                type:            new WagonType("Livestock Truck", 6),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 7
            new WagonSpec(
                name:            "Grain Truck",
                type:            new WagonType("Grain Truck", 7),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 8
            new WagonSpec(
                name:            "Oil Truck",
                type:            new WagonType("Oil Truck", 8),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 9
            new WagonSpec(
                name:            "Boards Truck",
                type:            new WagonType("Boards Truck", 9),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 10
            new WagonSpec(
                name:            "Plastic Truck",
                type:            new WagonType("Plastic Truck", 10),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 11
            new WagonSpec(
                name:            "Meat Truck",
                type:            new WagonType("Meat Truck", 11),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 12
            new WagonSpec(
                name:            "Flour Truck",
                type:            new WagonType("Flour Truck", 12),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 13
            new WagonSpec(
                name:            "Metals Truck",
                type:            new WagonType("Metals Truck", 13),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 14
            new WagonSpec(
                name:            "Glass Truck",
                type:            new WagonType("Glass Truck", 14),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 15
            new WagonSpec(
                name:            "Furniture Truck",
                type:            new WagonType("Furniture Truck", 15),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
            // Vagón 16
            new WagonSpec(
                name:            "Electronics Truck",
                type:            new WagonType("Electronics Truck", 16),
                cost:            1000,
                weight:          2,
                maximumCapacity: 5
            ),
        };

        // "Hash table" lookupy – tu má slovník zmysel, lebo ide o kolekciu typov.
        private static readonly Dictionary<string, WagonSpec> _byName = BuildByName();
        private static readonly Dictionary<int, WagonSpec> _byId = BuildById();

        private static Dictionary<string, WagonSpec> BuildByName()
        {
            var d = new Dictionary<string, WagonSpec>();
            foreach (var w in All) d[w.Type.Name] = w;
            return d;
        }

        private static Dictionary<int, WagonSpec> BuildById()
        {
            var d = new Dictionary<int, WagonSpec>();
            foreach (var w in All) d[w.Type.Id] = w;
            return d;
        }

        /// <summary>Názvy typov vagónov – priamo použiteľné pre WagonTypeDropdown.</summary>
        public static string[] Names()
        {
            var names = new string[All.Count];
            for (int i = 0; i < All.Count; i++) names[i] = All[i].Name;
            return names;
        }

        /// <summary>Vráti WagonSpec podľa indexu v dropdowne (bezpečné voči rozsahu).</summary>
        public static WagonSpec ByIndex(int index)
        {
            if (index < 0 || index >= All.Count) return null;
            return All[index];
        }

        /// <summary>Vráti WagonSpec podľa textového označenia ("Coal Truck"), alebo null.</summary>
        public static WagonSpec ByName(string name)
            => name != null && _byName.TryGetValue(name, out var w) ? w : null;

        /// <summary>Vráti WagonSpec podľa číselného označenia (1, 2, ...), alebo null.</summary>
        public static WagonSpec ById(int id)
            => _byId.TryGetValue(id, out var w) ? w : null;
    }
}

TrainSystem.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using Game.TrainStock;

/// <summary>
/// TrainSystem.cs
///
/// PARALELIZMUS a DEBOUNCE:
/// ─────────────────────────────────────────────────────────────────────────
/// ✔ A* pathfinding beží na Thread Pool cez Task.Run (AStarPathThreaded).
///   Pracuje s int[] snapshotom tileGrid – žiadne Unity API z vlákna.
///   Výsledky sa prenášajú cez ConcurrentQueue, aplikujú sa v Update().
///
/// ✔ OnMapChanged() – DEBOUNCE (riešenie trhnutia vlakov):
///   Každé volanie len nastaví flag + resetuje timer (_mapChangedDebounceTimer).
///   Žiadna práca na hlavnom vlákne pri kliku (žiadny SnapshotTileGrid).
///   Update() odpočítava timer; až po uplynutí DEBOUNCE_DELAY od posledného
///   kliku spustí SnapshotTileGrid() + Task.Run A* pre bežiace vlaky.
///   Vlaky sa hýbu ďalej po starej ceste počas čakania na debounce –
///   žiadne zastavenie, žiadne trhnutie.
///
///   Prepočítavajú sa len vlaky, ktorých aktuálna cesta mohla byť dotknutá
///   (isRunning && !isComputingPath). Vlaky, ktoré práve čakajú na stanici
///   alebo pri prerušení, sa neprepočítavajú zbytočne.
///
/// ✘ UpdateTrain / pohyb – hlavné vlákno (Unity API, thread-unsafe).
/// ✘ CreateTrain / RemoveTrain – hlavné vlákno (GameObject API).
/// ─────────────────────────────────────────────────────────────────────────
///
/// VLAKOVÁ SÚPRAVA – „Distance-Based Path System":
/// ─────────────────────────────────────────────────────────────────────────
/// • Trasa je reprezentovaná ako PathData (List<Vector3> bodov + predpočítané
///   kumulatívne dĺžky segmentov). Zdroj: A* ako List<Vector2Int> → konvertovaný
///   na waypointy cez BuildWaypointPath (centrá + hrany + krivkové rohy).
/// • Lokomotíva má skalárnu hodnotu locomotiveDistance (vzdialenosť pozdĺž cesty).
///   Každý frame: locomotiveDistance += speed * deltaTime.
/// • Vagón i má offsetDist = i * wagonSpacing za lokomotívou.
///   wagonDistance = locomotiveDistance - (i * wagonSpacing), min 0.
/// • Pozícia = PathData.GetPositionAtDistance(d)   → Vector3.Lerp na segmente.
/// • Rotácia = PathData.GetDirectionAtDistance(d)  → Quaternion.LookRotation.
/// • ŽIADNA história, ŽIADNE oneskorenie, ŽIADNE posHistory / historyAccum.
/// • Deterministické: rovnaký vstup → rovnaký výstup každý frame.
///
/// WAYPOINT-BASED MOVEMENT (NOVÉ):
///   Trasa už nie je center→center. Pre každý prechod A→B sa generuje:
///     A.center → A.edge(smer A→B) → B.edge(smer B→A) → B.center
///   Pri krivkovej dlaždici sa center NEPOUŽÍVA – generuje sa:
///     B.entryEdge → B.exitEdge  (priama diagonála cez vnútro dlaždice, ~45°)
///   Pri výhybkovej dlaždici (RailSwitch*) sa správanie líši podľa toho,
///   či ide o priamy prechod (proti-smery) alebo o odbočku (kolmé smery):
///     • PRIAMY: B.entryEdge → B.center → B.exitEdge (ako priama dlaždica)
///     • ODBOČKA: B.entryEdge → B.innerEntry → B.innerExit → B.exitEdge
///       (3 priame segmenty, stredný diagonálny ~45°; vizuálne železničný
///        turnout, BEZ bezier kriviek, BEZ splajnov, BEZ smoothingu.)
///   Pri križovatkovej dlaždici (RailCrossroad, 4 spojenia) platí to isté:
///     • PRIAMY (Top↔Bottom, Left↔Right): edge → center → edge (90°)
///     • ROHOVÝ (napr. Top↔Right): edge → innerEntry → innerExit → edge
///       (45° turnout, identický s odbočkou výhybky)
///   Krivka, odbočka výhybky aj rohový prechod križovatky používajú
///   VÝHRADNE priame čiarové segmenty spojené lineárnym Lerp-om medzi
///   waypointmi.
///
/// PREJAZD CEZ SVAH (LevelUp / LevelDown):
///   Terén je výškový raster v ROHOCH dlaždíc – jedna dlaždica je
///   naklonený quad medzi 4 rohovými vertexmi s rôznymi Y. Y-súradnica
///   každého waypointu sa získava biliniárnou interpoláciou výšok týchto
///   4 rohov (GetTileSurfaceY), takže každý bod na trase leží presne na
///   šikmej ploche dlaždice. Vlak na svahu ide pod skutočným uhlom
///   stúpania – uhol je daný geometriou terénu, nie pevnou konštantou.
///   Hladkosť na hrane medzi dlaždicami je zaručená automaticky:
///   A.right-edge a B.left-edge zdieľajú tie isté dva rohové vertexy,
///   takže ich interpolovaná Y vychádza identická → žiadny schod, žiadny
///   90° lom medzi dlaždicami. Logika svahu je transparentná pre všetky
///   typy dlaždíc – priame, krivky, výhybky, križovatky.
///
/// OBRAT SMERU NA STANICI:
///   path.Reverse()  +  locomotiveDistance = totalLength - locomotiveDistance
///   → okamžitý, presný, bez usadzovania.
///
/// PARAMETRIZÁCIA:
///   wagonCount   – počet vagónov (1 až 10, predvolene 5).
///   wagonSpacing – rozostup členov súpravy pozdĺž trajektórie (predvolene 0.85).
/// ─────────────────────────────────────────────────────────────────────────
/// </summary>
public class TrainSystem : MonoBehaviour
{
    public static TrainSystem instance;

    // =====================================================================
    // KONFIGURÁCIA VLAKOVEJ SÚPRAVY
    // =====================================================================

    [Range(1, 10)]
    public int wagonCount = 5;

    public float wagonSpacing = 0.85f;

    // =====================================================================
    // POMOCNÁ TRIEDA – PathData
    // Ukladá trasu ako Vector3 body + predpočítané kumulatívne vzdialenosti.
    // =====================================================================

    public class PathData
    {
        /// <summary>Body trasy (waypointy: centrá + hrany + krivkové rohy). Nemeniť po inicializácii.</summary>
        public readonly List<Vector3> points;

        /// <summary>
        /// cumulativeLengths[i] = vzdialenosť od points[0] po points[i].
        /// cumulativeLengths[0] == 0 vždy.
        /// </summary>
        public readonly float[] cumulativeLengths;

        /// <summary>Celková dĺžka trasy = cumulativeLengths[Count-1].</summary>
        public readonly float totalLength;

        public int Count => points.Count;

        public PathData(List<Vector3> pts)
        {
            points = pts;
            cumulativeLengths = new float[pts.Count];
            cumulativeLengths[0] = 0f;
            for (int i = 1; i < pts.Count; i++)
                cumulativeLengths[i] = cumulativeLengths[i - 1] + Vector3.Distance(pts[i - 1], pts[i]);
            totalLength = pts.Count > 0 ? cumulativeLengths[pts.Count - 1] : 0f;
        }

        /// <summary>
        /// Vráti pozíciu na trase pre zadanú vzdialenosť od začiatku.
        /// Vzdialenosť je upnutá na [0, totalLength].
        /// Interpolácia je VÝHRADNE lineárna (Vector3.Lerp) medzi dvoma susednými bodmi.
        /// </summary>
        public Vector3 GetPositionAtDistance(float dist)
        {
            if (points.Count == 0) return Vector3.zero;
            if (points.Count == 1) return points[0];

            dist = Mathf.Clamp(dist, 0f, totalLength);

            // Binárne vyhľadávanie segmentu
            int lo = 0, hi = points.Count - 1;
            while (lo < hi - 1)
            {
                int mid = (lo + hi) >> 1;
                if (cumulativeLengths[mid] <= dist) lo = mid;
                else hi = mid;
            }

            float segStart = cumulativeLengths[lo];
            float segEnd = cumulativeLengths[hi];
            float segLen = segEnd - segStart;

            if (segLen < 1e-6f) return points[hi];

            float localT = (dist - segStart) / segLen;
            return Vector3.Lerp(points[lo], points[hi], localT);
        }

        /// <summary>
        /// Vráti normalizovaný smer pohybu na zadanej vzdialenosti.
        /// Smer = (nextPoint - currentPoint).normalized pre segment, v ktorom leží dist.
        /// NIKDY neakumuluje rotácie, NIKDY nepoužíva dáta z predchádzajúceho snímku.
        /// </summary>
        public Vector3 GetDirectionAtDistance(float dist)
        {
            if (points.Count < 2) return Vector3.forward;

            dist = Mathf.Clamp(dist, 0f, totalLength);

            int lo = 0, hi = points.Count - 1;
            while (lo < hi - 1)
            {
                int mid = (lo + hi) >> 1;
                if (cumulativeLengths[mid] <= dist) lo = mid;
                else hi = mid;
            }

            Vector3 dir = points[hi] - points[lo];
            return dir.sqrMagnitude > 1e-12f ? dir.normalized : Vector3.forward;
        }

        /// <summary>
        /// Vráti obrátenú kópiu PathData (pre obrat smeru na stanici).
        /// Pôvodný objekt zostáva nezmenený.
        /// </summary>
        public PathData Reversed()
        {
            var rev = new List<Vector3>(points);
            rev.Reverse();
            return new PathData(rev);
        }
    }

    // =====================================================================
    // DÁTOVÉ ŠTRUKTÚRY
    // =====================================================================

    public class TrainData
    {
        public int depotX, depotZ;

        /// <summary>
        /// Dátový popis vlakovej súpravy – typ vlaku + jeho atribúty + vagóny
        /// a ich atribúty (Name, Cost, Speed, kapacity vagónov atď.).
        /// Definované v TrainStock.cs. Pohybový/pathfinding stav zostáva
        /// v poliach TrainData nižšie; toto je oddelená "obsahová" časť.
        ///
        /// Naplní sa pri CreateTrain podľa voľby z TrainTypeDropdown /
        /// WagonTypeDropdown. UI okno s detailmi vlaku si odtiaľto len číta.
        /// </summary>
        public TrainInstance consist;

        /// <summary>Skrytá kocka (Renderer off) – pohybová logika (pozícia hlavy).</summary>
        public GameObject trainObject;

        /// <summary>Vizuálna lokomotíva (CYAN kváder).</summary>
        public GameObject locomotive;

        /// <summary>Vizuálne vagóny (GREY kvádre). Count = wagonCount pri vytvorení.</summary>
        public List<GameObject> wagons = new List<GameObject>();

        // ------------------------------------------------------------------
        // DISTANCE-BASED PATH STATE
        // ------------------------------------------------------------------

        /// <summary>
        /// Aktuálna trasa ako PathData (waypointy + kumulatívne dĺžky).
        /// null = žiadna aktívna trasa.
        /// </summary>
        public PathData activePath;

        /// <summary>
        /// Vzdialenosť lokomotívy od začiatku activePath.
        /// Každý frame: locomotiveDistance += trainSpeed * Time.deltaTime.
        /// </summary>
        public float locomotiveDistance;

        /// <summary>Rýchlosť pohybu vlaku (jednotky/sekunda). Odvodená z MOVE_TIME.</summary>
        public float trainSpeed;

        // ------------------------------------------------------------------
        // Pôvodné polia TrainData (nezmenené)
        // ------------------------------------------------------------------
        public List<Vector2Int> stations;
        public int currentStationIndex;
        public bool isRunning;
        public bool isWaiting;
        public bool isReturningToDepot;
        public bool isAtDepot;
        public bool reverseDirection;
        public List<Vector2Int> currentPath;
        public int pathIndex;
        public Vector2Int currentTile;
        public float moveTimer;
        public float waitTimer;
        public bool isComputingPath;
        public List<Vector2Int> pendingPath;
        public bool isGoingToDepotViaStation;
        public bool isStoppedAwaitingDepotReturn;
        public bool pendingReturnToDepot;

        // ------------------------------------------------------------------
        // OBCHOD NA STANICI (TradeSystem)
        // ------------------------------------------------------------------
        /// <summary>
        /// True, ak v AKTUÁLNEJ zastávke na stanici už prebehol pokus o
        /// transakciu (výmenu tovaru). Bráni tomu, aby sa obchod spustil
        /// každý frame – spustí sa práve raz, po 2 s čakania. Resetuje sa
        /// pri každom novom príchode na stanicu (OnPathComplete).
        /// </summary>
        public bool tradeDoneAtStation;

        /// <summary>Index v currentPath kde activePath zacina. Pouziva sa v UpdateCurrentTile.</summary>
        public int pathIndexAtPathStart;

        /// <summary>
        /// Mapovanie waypoint-index → currentPath-index.
        /// Pre activePath.points[i] hovorí, ku ktorej dlaždici v currentPath waypoint patrí.
        /// Používa sa v UpdateCurrentTile pri waypoint-based pohybe.
        /// </summary>
        public int[] waypointToTileIdx;

        public TrainData(int dx, int dz)
        {
            depotX = dx; depotZ = dz;
            stations = new List<Vector2Int>();
            currentStationIndex = 0;
            isRunning = false; isWaiting = false;
            isReturningToDepot = false; isAtDepot = true;
            reverseDirection = false;
            currentPath = new List<Vector2Int>();
            pathIndex = 0;
            currentTile = new Vector2Int(dx, dz);
            moveTimer = 0f; waitTimer = 0f;
            isComputingPath = false;
            pendingPath = null;
            isGoingToDepotViaStation = false;
            isStoppedAwaitingDepotReturn = false;
            pendingReturnToDepot = false;
            tradeDoneAtStation = false;

            activePath = null;
            locomotiveDistance = 0f;
            trainSpeed = 0f;
            pathIndexAtPathStart = 0;
            waypointToTileIdx = null;

            consist = null;
        }
    }

    // =====================================================================
    // VÝSLEDKOVÁ FRONTA
    // =====================================================================

    enum PathDestination { Station, Depot, ReturnViaStation }

    struct PathResult
    {
        public int depotKey;
        public List<Vector2Int> path;
        public bool isReturnToDepot;
        public PathDestination destination;
    }

    public enum ReturnToDepotResult { Dispatched, StoppedAwaitingSecondR, Error }

    readonly System.Collections.Concurrent.ConcurrentQueue<PathResult> _pendingPathResults
        = new System.Collections.Concurrent.ConcurrentQueue<PathResult>();

    // =====================================================================
    // DEBOUNCE
    // =====================================================================

    const float DEBOUNCE_DELAY = 0.4f;
    bool _mapChangePending = false;
    float _mapChangedDebounceTimer = 0f;

    // =====================================================================
    // SNAPSHOT TILE GRIDU
    // =====================================================================

    const int GRID_SIZE = 256;

    /// <summary>
    /// Snapshot tileGrid. Obsahuje DVA paralelné polia:
    ///   tileIDs[i]     – tileID dlaždice (0 = empty, 1 = rail, 2 = station, 3 = depot)
    ///   connections[i] – DirectionMask dlaždice (uložený ako int pre vlákno-bezpečné použitie)
    /// Indexovanie: i = z * GRID_SIZE + x
    /// </summary>
    struct TileGridSnapshot
    {
        public int[] tileIDs;
        public int[] connections;
    }

    TileGridSnapshot SnapshotTileGrid()
    {
        var snap = new TileGridSnapshot
        {
            tileIDs = new int[GRID_SIZE * GRID_SIZE],
            connections = new int[GRID_SIZE * GRID_SIZE]
        };

        for (int x = 0; x < GRID_SIZE; x++)
        {
            for (int z = 0; z < GRID_SIZE; z++)
            {
                var td = IndicatrixAPI.instance.GetTileByIndex(x, z);
                int idx = z * GRID_SIZE + x;
                snap.tileIDs[idx] = td.tileID;
                snap.connections[idx] = (int)td.connections;
            }
        }
        return snap;
    }

    // =====================================================================
    // KONŠTANTY A STAV
    // =====================================================================

    Dictionary<int, TrainData> trains = new Dictionary<int, TrainData>();

    /// <summary>Čas prechodu jednej dlaždice v sekundách (1.0 = 1 s/dlaždica).</summary>
    const float MOVE_TIME = 2.0f;
    const float STATION_WAIT = 10.0f;
    const float BREAK_WAIT = 1.0f;

    /// <summary>
    /// Po koľkých sekundách čakania na cieľovej stanici vlak realizuje
    /// výmenu tovaru (TradeSystem). Zadanie: štandardné čakanie je 10 s
    /// (STATION_WAIT), transakcia prebehne po 2 s od príchodu.
    ///
    /// Trigger je naviazaný na UPLYNULÝ čas: spustí sa, keď
    /// (STATION_WAIT - waitTimer) >= TRADE_DELAY, t.j. po 2 s čakania.
    /// </summary>
    const float TRADE_DELAY = 2.0f;

    /// <summary>Rozmery kvádra lokomotívy aj vagónov.</summary>
    static readonly Vector3 CONSIST_SCALE = new Vector3(0.3f, 0.3f, 0.7f);

    void Awake()
    {
        instance = this;
        wagonCount = Mathf.Clamp(wagonCount, 1, 10);
    }

    // =====================================================================
    // POMOCNÉ
    // =====================================================================

    int DepotKey(int x, int z) => x * 10000 + z;

    Vector3 TileCenter(Vector2Int tile)
    {
        // Stred dlaždice: (u, v) = (0.5, 0.5) v lokálnych súradniciach.
        // Y sa získa biliniárnou interpoláciou výšok 4 rohov dlaždice
        // → priemer výšok všetkých 4 rohov (na rovine konštanta, na svahu
        // sa správne škáluje).
        float y = GetTileSurfaceY(tile, 0.5f, 0.5f) + TRACK_OFFSET_Y;
        return new Vector3(tile.x + 0.5f, y, tile.y + 0.5f);
    }

    float GetTerrainY(int x, int z)
    {
        try
        {
            int width = TerrainManager.instance.terrainWidth + 1;
            int index = z * width + x;
            if (index >= 0 && index < TerrainManager.instance.coordsF.Length)
                return TerrainManager.instance.coordsF[index].y;
        }
        catch { }
        return 0f;
    }

    /// <summary>Vertikálny offset koľaje nad povrchom terénu (track height).</summary>
    const float TRACK_OFFSET_Y = 0.3f;

    /// <summary>
    /// Biliniárna interpolácia výšky terénu vnútri jednej dlaždice (tile)
    /// medzi 4 rohovými vertexmi.
    ///
    /// Lokálne súradnice (u, v) ∈ [0, 1] × [0, 1]:
    ///   (u=0, v=0) = ľavý-dolný roh dlaždice  → vertex (tile.x,   tile.y)
    ///   (u=1, v=0) = pravý-dolný roh dlaždice → vertex (tile.x+1, tile.y)
    ///   (u=0, v=1) = ľavý-horný roh dlaždice  → vertex (tile.x,   tile.y+1)
    ///   (u=1, v=1) = pravý-horný roh dlaždice → vertex (tile.x+1, tile.y+1)
    /// kde u zodpovedá X-osi (Left→Right), v zodpovedá Z-osi (Bottom→Top).
    ///
    /// Vzorec biliniárnej interpolácie:
    ///   y = (1-u)(1-v) * h00 + u*(1-v) * h10 + (1-u)*v * h01 + u*v * h11
    ///
    /// PREČO TOTO POTREBUJEME (kopce, LevelUp / LevelDown):
    ///   Aktuálny terénny model ukladá výšku v ROHOCH dlaždice. Jedna
    ///   dlaždica je naklonený quad medzi 4 rohmi s rôznymi Y. Ak by sme
    ///   pre celý waypoint v rámci dlaždice používali iba výšku jedného
    ///   rohu (napr. ľavého-dolného), všetky waypointy v dlaždici by mali
    ///   rovnaké Y → vlak ide vodorovne a na hrane medzi dlaždicami
    ///   "vyskočí" na novú výšku → ostrý 90° schod namiesto 45° svahu.
    ///
    ///   Biliniárnou interpoláciou dostáva každý waypoint správnu výšku
    ///   podľa svojej polohy (u, v) na šikmej ploche, takže pohyb cez
    ///   svah je plynulý a uhol stúpania presne zodpovedá skutočnému
    ///   prevýšeniu medzi rohmi terénu.
    ///
    /// HLADKOSŤ NA HRANÁCH MEDZI DLAŽDICAMI:
    ///   Pre dve susediace dlaždice A a B (B = A + Right) platí:
    ///     A.exitEdge(Right)  → použije priemer výšok dvoch pravých rohov A
    ///     B.entryEdge(Left)  → použije priemer výšok dvoch ľavých rohov B
    ///   Ľavé rohy B sú TOTOŽNÉ vertexy ako pravé rohy A → výška vychádza
    ///   identická. Prechod cez hranu je teda C0-spojitý automaticky.
    ///   Žiadna explicitná konštanta uhla ani manuálne dorovnávanie nie je
    ///   potrebné – uhol svahu je daný geometriou terénu.
    ///
    /// Funkcia je deterministická a thread-safe pokiaľ ide o vstupy
    /// (číta len TerrainManager.instance.coordsF, ktoré sa nemení mimo
    /// hlavného vlákna v rámci jedného frame-u).
    /// </summary>
    float GetTileSurfaceY(Vector2Int tile, float u, float v)
    {
        float h00 = GetTerrainY(tile.x, tile.y);     // ľavý-dolný roh
        float h10 = GetTerrainY(tile.x + 1, tile.y);     // pravý-dolný roh
        float h01 = GetTerrainY(tile.x, tile.y + 1); // ľavý-horný roh
        float h11 = GetTerrainY(tile.x + 1, tile.y + 1); // pravý-horný roh

        float omu = 1f - u;
        float omv = 1f - v;

        return omu * omv * h00
             + u * omv * h10
             + omu * v * h01
             + u * v * h11;
    }

    int GetTileID(int x, int z)
    {
        if (x < 0 || x >= GRID_SIZE || z < 0 || z >= GRID_SIZE) return -1;
        return IndicatrixAPI.instance.GetTileByIndex(x, z).tileID;
    }

    bool IsPassable(int x, int z)
    {
        int id = GetTileID(x, z);
        return id == 1 || id == 2 || id == 3;
    }

    // =====================================================================
    // DIRECTION HELPERS – mapovanie Vector2Int <-> DirectionMask
    // =====================================================================

    /// <summary>
    /// Konvertuje 4-smerový vektor (zo step v A*) na DirectionMask.
    /// (+1, 0)  → Right
    /// (-1, 0)  → Left
    /// ( 0,+1)  → Top
    /// ( 0,-1)  → Bottom
    /// </summary>
    static IndicatrixAPI.DirectionMask DirFromStep(Vector2Int step)
    {
        if (step.x == 1 && step.y == 0) return IndicatrixAPI.DirectionMask.Right;
        if (step.x == -1 && step.y == 0) return IndicatrixAPI.DirectionMask.Left;
        if (step.x == 0 && step.y == 1) return IndicatrixAPI.DirectionMask.Top;
        if (step.x == 0 && step.y == -1) return IndicatrixAPI.DirectionMask.Bottom;
        return IndicatrixAPI.DirectionMask.None;
    }

    /// <summary>
    /// Vráti smer pohybu z dlaždice 'from' na dlaždicu 'to' (musia byť susedia).
    /// </summary>
    static IndicatrixAPI.DirectionMask GetDirection(Vector2Int from, Vector2Int to)
    {
        return DirFromStep(to - from);
    }

    /// <summary>
    /// Pre danú dlaždicu (x, z) a smer 'dir' vráti svetový bod uprostred danej hrany dlaždice.
    /// Hrana je medzi vnútrom dlaždice a susedom v smere 'dir'.
    ///   Right  → x = tile.x + 1.0, z = tile.z + 0.5
    ///   Left   → x = tile.x + 0.0, z = tile.z + 0.5
    ///   Top    → x = tile.x + 0.5, z = tile.z + 1.0
    ///   Bottom → x = tile.x + 0.5, z = tile.z + 0.0
    /// Y: biliniárna interpolácia výšky šikmej plochy dlaždice v polohe
    ///    stredu danej hrany. Pre rovinu vychádza priemer 4 rohov; pre svah
    ///    (LevelUp/LevelDown) vychádza presne stred danej hrany dlaždice,
    ///    takže vlak na svahu ide pod skutočným uhlom stúpania – nie po
    ///    schodoch s 90° lomami.
    /// Hladkosť na hrane medzi susednými dlaždicami je zaručená
    /// automaticky: A.right-edge a B.left-edge zdieľajú tie isté dva
    /// rohové vertexy → ich priemer je totožný.
    /// </summary>
    Vector3 EdgePoint(Vector2Int tile, IndicatrixAPI.DirectionMask dir)
    {
        float fx = tile.x, fz = tile.y;
        switch (dir)
        {
            case IndicatrixAPI.DirectionMask.Right:
                return new Vector3(fx + 1.0f, GetTileSurfaceY(tile, 1.0f, 0.5f) + TRACK_OFFSET_Y, fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Left:
                return new Vector3(fx + 0.0f, GetTileSurfaceY(tile, 0.0f, 0.5f) + TRACK_OFFSET_Y, fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Top:
                return new Vector3(fx + 0.5f, GetTileSurfaceY(tile, 0.5f, 1.0f) + TRACK_OFFSET_Y, fz + 1.0f);
            case IndicatrixAPI.DirectionMask.Bottom:
                return new Vector3(fx + 0.5f, GetTileSurfaceY(tile, 0.5f, 0.0f) + TRACK_OFFSET_Y, fz + 0.0f);
            default: return TileCenter(tile);
        }
    }

    /// <summary>
    /// Detekcia, či je daná dlaždica krivkou.
    /// Krivka = presne 2 nastavené smery, ktoré NIE SÚ navzájom opačné
    /// (t.j. nie Left+Right ani Top+Bottom).
    /// </summary>
    static bool IsCurveTile(IndicatrixAPI.DirectionMask conns)
    {
        // Počet bitov
        int bits = 0;
        if ((conns & IndicatrixAPI.DirectionMask.Left) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Right) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Top) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Bottom) != 0) bits++;
        if (bits != 2) return false;

        bool horiz = (conns & (IndicatrixAPI.DirectionMask.Left | IndicatrixAPI.DirectionMask.Right))
                       == (IndicatrixAPI.DirectionMask.Left | IndicatrixAPI.DirectionMask.Right);
        bool vert = (conns & (IndicatrixAPI.DirectionMask.Top | IndicatrixAPI.DirectionMask.Bottom))
                       == (IndicatrixAPI.DirectionMask.Top | IndicatrixAPI.DirectionMask.Bottom);

        return !horiz && !vert;
    }

    /// <summary>
    /// Detekcia, či je daná dlaždica výhybkou (rail switch / turnout).
    /// Výhybka má presne 3 nastavené smery:
    ///   • RailSwitchHorizontalBottom: Left + Right + Bottom
    ///   • RailSwitchHorizontalTop:    Left + Right + Top
    ///   • RailSwitchVerticalBottom:   Top + Bottom + Right
    ///   • RailSwitchVerticalTop:      Top + Bottom + Left
    ///
    /// Výhybka je plne obojsmerná – ktorékoľvek dva z troch smerov sa
    /// môžu navzájom prepojiť. Pohyb cez výhybku má dva režimy:
    ///   1. Priamy prechod (proti-smery, napr. Left↔Right) – správa sa ako
    ///      klasická priama koľaj: edge → center → edge.
    ///   2. Odbočka (kolmé smery, napr. Left↔Bottom) – generuje sa diagonálny
    ///      vnútorný layout (~45° turnout), nie ostrý 90° roh ako pri krivke.
    ///
    /// Všetky 4 križovatkové smery (crossroad) majú 4 bity, takže sú
    /// odlíšené: switch == 3 bity, crossroad == 4 bity.
    /// </summary>
    static bool IsSwitchTile(IndicatrixAPI.DirectionMask conns)
    {
        int bits = 0;
        if ((conns & IndicatrixAPI.DirectionMask.Left) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Right) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Top) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Bottom) != 0) bits++;
        return bits == 3;
    }

    /// <summary>
    /// Detekcia, či je daná dlaždica plnou križovatkou (RailCrossroad).
    /// Križovatka má všetky 4 smery (Left + Right + Top + Bottom) –
    /// jediný typ s 4 bitmi v DirectionMask.
    ///
    /// Pohyb cez križovatku má dva režimy podľa entry/exit smerov:
    ///   1. Priamy prechod (proti-smery, 90°):
    ///         Top ↔ Bottom    (vertikálny prejazd)
    ///         Left ↔ Right    (horizontálny prejazd)
    ///      → edge → center → edge (klasická priama)
    ///   2. Rohový prechod (kolmé smery, 45°):
    ///         Top ↔ Right, Top ↔ Left
    ///         Bottom ↔ Right, Bottom ↔ Left
    ///      → edge → innerEntry → innerExit → edge (45° turnout layout,
    ///        identický s odbočkou výhybky)
    /// </summary>
    static bool IsCrossroadTile(IndicatrixAPI.DirectionMask conns)
    {
        const IndicatrixAPI.DirectionMask ALL =
            IndicatrixAPI.DirectionMask.Left
          | IndicatrixAPI.DirectionMask.Right
          | IndicatrixAPI.DirectionMask.Top
          | IndicatrixAPI.DirectionMask.Bottom;
        return (conns & ALL) == ALL;
    }

    /// <summary>
    /// Vráti true, ak sú dva smery navzájom kolmé (jeden horizontálny + jeden
    /// vertikálny). Vracia false ak sú totožné, alebo proti-smery, alebo None.
    /// Používa sa na odlíšenie odbočky vs priameho prechodu cez výhybku.
    /// </summary>
    static bool IsPerpendicular(IndicatrixAPI.DirectionMask a, IndicatrixAPI.DirectionMask b)
    {
        bool aHoriz = a == IndicatrixAPI.DirectionMask.Left || a == IndicatrixAPI.DirectionMask.Right;
        bool aVert = a == IndicatrixAPI.DirectionMask.Top || a == IndicatrixAPI.DirectionMask.Bottom;
        bool bHoriz = b == IndicatrixAPI.DirectionMask.Left || b == IndicatrixAPI.DirectionMask.Right;
        bool bVert = b == IndicatrixAPI.DirectionMask.Top || b == IndicatrixAPI.DirectionMask.Bottom;
        return (aHoriz && bVert) || (aVert && bHoriz);
    }

    /// <summary>
    /// Vnútorný bod výhybky pre 45° turnout geometriu.
    ///
    /// Pre danú dlaždicu, smer (z perspektívy hrany dlaždice, t.j. v ktorej
    /// hrane sa nachádzame) a parameter t ∈ (0, 0.5) vráti bod ležiaci na osi
    /// danej hrany, ale posunutý dovnútra dlaždice o vzdialenosť t.
    ///
    /// Príklady (tile na (0,0), t = 0.25):
    ///   dir = Left   → edge je (0.0, y, 0.5), inner = (0.25, y, 0.5)
    ///   dir = Right  → edge je (1.0, y, 0.5), inner = (0.75, y, 0.5)
    ///   dir = Top    → edge je (0.5, y, 1.0), inner = (0.5,  y, 0.75)
    ///   dir = Bottom → edge je (0.5, y, 0.0), inner = (0.5,  y, 0.25)
    ///
    /// Použitie: pre odbočkový prechod cez výhybku skladáme cestu ako
    ///   entryEdge → innerEntry(t) → innerExit(t) → exitEdge
    /// Stredný segment (innerEntry → innerExit) je pri t = 0.25 a kolmých
    /// smeroch presne 45° diagonálny – vizuálne pripomína skutočný turnout
    /// koľajnice, bez ostrého 90° lomu cez stred dlaždice.
    /// </summary>
    Vector3 SwitchInnerEdgePoint(Vector2Int tile, IndicatrixAPI.DirectionMask dir, float t)
    {
        float fx = tile.x, fz = tile.y;
        switch (dir)
        {
            case IndicatrixAPI.DirectionMask.Right:
                return new Vector3(fx + 1.0f - t,
                    GetTileSurfaceY(tile, 1.0f - t, 0.5f) + TRACK_OFFSET_Y,
                    fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Left:
                return new Vector3(fx + 0.0f + t,
                    GetTileSurfaceY(tile, 0.0f + t, 0.5f) + TRACK_OFFSET_Y,
                    fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Top:
                return new Vector3(fx + 0.5f,
                    GetTileSurfaceY(tile, 0.5f, 1.0f - t) + TRACK_OFFSET_Y,
                    fz + 1.0f - t);
            case IndicatrixAPI.DirectionMask.Bottom:
                return new Vector3(fx + 0.5f,
                    GetTileSurfaceY(tile, 0.5f, 0.0f + t) + TRACK_OFFSET_Y,
                    fz + 0.0f + t);
            default: return TileCenter(tile);
        }
    }

    // =====================================================================
    // CAN MOVE – validácia smeru pre A* (thread-safe – pracuje so snapshotom)
    // =====================================================================

    /// <summary>
    /// Validácia A* prechodu z 'fromTile' na 'toTile' v smere 'direction'.
    /// Pravidlá:
    ///   1. toTile musí byť priechodná (rail/station/depot)
    ///   2. fromTile musí mať connection v smere 'direction'
    ///   3. toTile musí mať connection v opačnom smere (Opposite(direction))
    ///   4. Výnimka pre štart: ak je fromTile depo (tileID == 3), neaplikujeme
    ///      pravidlo č. 2 striktne (depo má jeden výstup, ten musí ladiť so smerom);
    ///      táto výnimka NIE JE potrebná – depo má vlastný DirectionMask, takže
    ///      vlak môže opustiť depo iba povoleným smerom. Ak by toto bolo príliš
    ///      reštriktívne, dá sa relaxovať.
    /// </summary>
    static bool CanMove(int[] tileIDs, int[] conns, Vector2Int fromTile, Vector2Int toTile,
                        IndicatrixAPI.DirectionMask direction)
    {
        if (toTile.x < 0 || toTile.x >= GRID_SIZE || toTile.y < 0 || toTile.y >= GRID_SIZE)
            return false;
        if (fromTile.x < 0 || fromTile.x >= GRID_SIZE || fromTile.y < 0 || fromTile.y >= GRID_SIZE)
            return false;

        int toIdx = toTile.y * GRID_SIZE + toTile.x;
        int toID = tileIDs[toIdx];
        if (!(toID == 1 || toID == 2 || toID == 3)) return false;

        int fromIdx = fromTile.y * GRID_SIZE + fromTile.x;
        IndicatrixAPI.DirectionMask fromConn = (IndicatrixAPI.DirectionMask)conns[fromIdx];
        IndicatrixAPI.DirectionMask toConn = (IndicatrixAPI.DirectionMask)conns[toIdx];

        // fromTile musí povoľovať odchod v smere direction
        if ((fromConn & direction) == 0) return false;

        // toTile musí povoľovať vstup z opačného smeru
        IndicatrixAPI.DirectionMask oppositeDir = IndicatrixAPI.Opposite(direction);
        if ((toConn & oppositeDir) == 0) return false;

        return true;
    }

    // =====================================================================
    // BUILD WAYPOINT PATH – konvertuje List<Vector2Int> na waypointy
    // =====================================================================

    /// <summary>
    /// Z tile-cesty vygeneruje waypoint cestu podľa pravidiel:
    ///
    /// Pre každú dlaždicu B medzi predchádzajúcou A a nasledujúcou C:
    ///   • Ak je B priama (rail/station/depot s opačnými smermi):
    ///       waypoint sekvencia: B.entryEdge, B.center, B.exitEdge
    ///   • Ak je B krivka (RailCurveX):
    ///       waypoint sekvencia: B.entryEdge, B.exitEdge
    ///       (center sa NEPOUŽÍVA, vnútorný roh sa NEPOUŽÍVA – PathData
    ///       ich spojí jedným lineárnym Lerp segmentom, ktorý prechádza
    ///       vnútrom dlaždice diagonálne pod ~45°.)
    ///   • Ak je B výhybka (RailSwitch*, 3 spojenia):
    ///       - PRIAMY prechod (proti-smery): B.entryEdge, B.center, B.exitEdge
    ///         (rovnako ako priama dlaždica)
    ///       - ODBOČKA (kolmé smery): B.entryEdge, B.innerEntry,
    ///         B.innerExit, B.exitEdge
    ///         (4 waypointy → 3 priame segmenty, stredný 45° diagonálny;
    ///          vizuálne 45° turnout, NIE ostrý 90° roh.)
    ///   • Ak je B križovatka (RailCrossroad, 4 spojenia):
    ///       - PRIAMY prechod (proti-smery, Top↔Bottom alebo Left↔Right):
    ///         B.entryEdge, B.center, B.exitEdge (90° prejazd cez stred)
    ///       - ROHOVÝ prechod (kolmé smery, napr. Top↔Right):
    ///         B.entryEdge, B.innerEntry, B.innerExit, B.exitEdge
    ///         (45° turnout layout, identický s odbočkou výhybky)
    ///
    /// Štart a koniec:
    ///   • Prvá dlaždica (štart): pridá sa A.center, potom A.exitEdge.
    ///   • Posledná dlaždica (cieľ): pridá sa B.entryEdge, potom B.center.
    ///
    /// Pre jednodlaždičovú cestu (count==1): vráti len [center].
    ///
    /// HRANIČNÉ WAYPOINTY (kritické):
    ///   Tile-prechod A→B vygeneruje DVA waypointy s identickou Vector3
    ///   pozíciou, ktoré sú VŽDY zachované ako dva samostatné body:
    ///     - A.exitEdge(dir)         ← patrí dlaždici A
    ///     - B.entryEdge(opposite)   ← patrí dlaždici B
    ///   Aj keď sú geometricky totožné, logicky reprezentujú prechod cez
    ///   hranu a poradie je dôležité: zachováva smerovosť a umožňuje
    ///   správne priradenie currentTile cez waypointToTileIdx. Krivka,
    ///   výhybka aj križovatka spotrebúvajú SVOJ vstupný edge waypoint pre
    ///   korektné odvodenie exit-smeru. Z toho dôvodu deduplikujeme len
    ///   waypointy z ROVNAKEJ dlaždice (pozri AddWaypoint).
    ///
    /// VÝSTUP:
    ///   waypoints – List<Vector3> waypointov v poradí pohybu
    ///   tileIdxPerWaypoint – pre každý waypoint index do tilePath, ku ktorému patrí
    ///                        (potrebné pre UpdateCurrentTile)
    /// </summary>
    void BuildWaypointPath(List<Vector2Int> tilePath, out List<Vector3> waypoints,
                           out List<int> tileIdxPerWaypoint)
    {
        // Pracujeme cez lokálne premenné, nie cez 'out' parametre. Lokálna
        // funkcia AddWaypoint nemôže zachytávať 'out'/'ref' parametre
        // (CS1628), preto vyplníme lokálne zoznamy a na konci ich priradíme
        // do 'out' parametrov.
        var wp = new List<Vector3>();
        var tIdx = new List<int>();

        if (tilePath == null || tilePath.Count == 0)
        {
            waypoints = wp;
            tileIdxPerWaypoint = tIdx;
            return;
        }

        if (tilePath.Count == 1)
        {
            wp.Add(TileCenter(tilePath[0]));
            tIdx.Add(0);
            waypoints = wp;
            tileIdxPerWaypoint = tIdx;
            return;
        }

        // Pomocný local fn na pridanie waypointu.
        //
        // PRAVIDLO (kritické pre tile-boundary semantiku):
        // Waypointy s rovnakou Vector3 pozíciou sa NESMÚ zlučovať,
        // ak patria DVOM RÔZNYM dlaždiciam. Konkrétne A.exitEdge(dir) a
        // B.entryEdge(opposite(dir)) sú v priestore identické, ale logicky
        // ide o dva po sebe idúce waypointy:
        //   - A.exitEdge patrí dlaždici A (tileIdx = A)
        //   - B.entryEdge patrí dlaždici B (tileIdx = B)
        // Toto poradie zachováva smerovosť cesty a umožňuje korektné
        // priradenie currentTile pri prekročení hranice (UpdateCurrentTile
        // používa waypointToTileIdx mapovanie). Bez toho sa hraničný
        // waypoint krivkovej dlaždice "stratí" a UpdateCurrentTile by
        // hlásil zlú dlaždicu pre frame priamo po prekročení hrany.
        //
        // Deduplikujeme IBA vtedy, keď sú obidva waypointy z TEJ ISTEJ
        // dlaždice a v priestore sa kryjú – vtedy ide o degenerovaný
        // intra-tile prípad bez dopadu na sémantiku.
        void AddWaypoint(Vector3 pt, int tileIdx)
        {
            if (wp.Count > 0)
            {
                Vector3 last = wp[wp.Count - 1];
                int lastTileIdx = tIdx[tIdx.Count - 1];
                bool sameTile = (lastTileIdx == tileIdx);
                bool samePos = (pt - last).sqrMagnitude < 1e-8f;
                if (sameTile && samePos) return; // intra-tile duplikát → preskoč
            }
            wp.Add(pt);
            tIdx.Add(tileIdx);
        }

        for (int i = 0; i < tilePath.Count; i++)
        {
            Vector2Int tile = tilePath[i];
            var conns = IndicatrixAPI.instance.GetTileByIndex(tile.x, tile.y).connections;

            bool isFirst = (i == 0);
            bool isLast = (i == tilePath.Count - 1);

            // Smer odchodu z aktuálnej dlaždice (do nasledujúcej)
            IndicatrixAPI.DirectionMask outDir = IndicatrixAPI.DirectionMask.None;
            if (!isLast) outDir = GetDirection(tile, tilePath[i + 1]);

            // Smer príchodu do aktuálnej dlaždice (z predchádzajúcej)
            IndicatrixAPI.DirectionMask inDir = IndicatrixAPI.DirectionMask.None;
            if (!isFirst) inDir = IndicatrixAPI.Opposite(GetDirection(tilePath[i - 1], tile));

            // ────────────────────────────────────────────────────────────────
            // KRIVKA: entryEdge → exitEdge  (BEZ centra, BEZ rohového bodu)
            //
            // PathData spojí dva po sebe idúce waypointy lineárnym Lerp-om.
            // Keďže entryEdge je stred jednej hrany dlaždice a exitEdge je
            // stred susediacej (kolmnej) hrany, priamka medzi nimi prechádza
            // vnútrom dlaždice diagonálne — vizuálne pod ~45°. To je presne
            // požadovaný "ostrý" angular pohyb cez krivku.
            //
            // Predtým sme sem vkladali aj rohový bod (vrchol bunky), čo
            // produkovalo dva segmenty s ostrým 90° lomom v rohu — pohyb
            // skákal cez vrchol dlaždice von z dráhy. Rohový bod sa preto
            // už NEpridáva.
            //
            // Funkcia CurveCornerPoint zostáva v zdrojáku pre prípadné
            // budúce vizuálne pomocníky, ale BuildWaypointPath ju NEvolá.
            // ────────────────────────────────────────────────────────────────
            if (!isFirst && !isLast && IsCurveTile(conns))
            {
                // entryEdge je geometricky totožný bod ako A.exitEdge predchádzajúcej
                // dlaždice, ale logicky patrí TEJTO krivkovej dlaždici –
                // AddWaypoint ho zachová ako samostatný waypoint, pretože tileIdx
                // je iný od predchádzajúceho waypointu.
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
                continue;
            }

            // ────────────────────────────────────────────────────────────────
            // VÝHYBKA (RAIL SWITCH) – 3 spojenia, plne obojsmerná.
            //
            // Vnútorná logika výhybky závisí od toho, či ide o priamy prechod
            // alebo o odbočku:
            //
            //   1. PRIAMY PRECHOD (proti-smery, napr. Left↔Right na
            //      RailSwitchHorizontalBottom alebo Top↔Bottom na
            //      RailSwitchVerticalBottom):
            //         entryEdge → center → exitEdge
            //      Identické správanie ako klasická priama koľaj. Stred
            //      dlaždice je súčasťou cesty.
            //
            //   2. ODBOČKA (kolmé smery, napr. Left↔Bottom alebo Right↔Top):
            //         entryEdge → innerEntry(t) → innerExit(t) → exitEdge
            //      kde t = SWITCH_TURNOUT_T (= 0.25). Vizuálne to vytvorí
            //      pohyb, ktorý:
            //         • vstúpi do dlaždice po hrane vstupu,
            //         • prejde krátky úsek pozdĺž osi vstupnej hrany,
            //         • diagonálne (45°) sa stočí dovnútra,
            //         • prejde krátky úsek pozdĺž osi výstupnej hrany,
            //         • opustí dlaždicu cez výstupnú hranu.
            //      Pohyb používa IBA priame čiarové segmenty (žiadne
            //      bezier krivky, žiadne splajny, žiadna interpolácia okrem
            //      lineárneho Lerp medzi dvoma susednými waypointmi).
            //      Vizuálne to pripomína skutočný železničný výhybkový
            //      úsek (turnout) namiesto ostrého 90° rohu cez stred.
            //
            // Tile-prechodové pravidlá zostávajú nezmenené:
            //   A.center → A.edge → B.edge   (medzi-tile)
            // entryEdge a exitEdge sú ŠTANDARDNÉ hranové waypointy zhodné
            // s ostatnými tile-typmi → tile-boundary semantika je zachovaná.
            // ────────────────────────────────────────────────────────────────
            if (!isFirst && !isLast && IsSwitchTile(conns))
            {
                // ── ODBOČKA: entry a exit sú kolmé smery ─────────────────
                if (IsPerpendicular(inDir, outDir))
                {
                    const float SWITCH_TURNOUT_T = 0.25f;
                    AddWaypoint(EdgePoint(tile, inDir), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, inDir, SWITCH_TURNOUT_T), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, outDir, SWITCH_TURNOUT_T), i);
                    AddWaypoint(EdgePoint(tile, outDir), i);
                    continue;
                }

                // ── PRIAMY PRECHOD: entry a exit sú proti-smery ─────────
                // (napr. Left ↔ Right na horizontálnej výhybke)
                // → správa sa ako klasická priama dlaždica: edge → center → edge
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
                continue;
            }

            // ────────────────────────────────────────────────────────────────
            // KRIŽOVATKA (RAIL CROSSROAD) – 4 spojenia (všetky 4 smery).
            //
            // Plná 4-cestná križovatka. Pohyb cez ňu závisí od entry/exit
            // smerov rovnako ako pri výhybke, len s jedným spojením naviac:
            //
            //   1. PRIAMY PRECHOD – 90° osový prejazd (proti-smery):
            //         • Top    ↔ Bottom   (vertikálny prejazd)
            //         • Left   ↔ Right    (horizontálny prejazd)
            //      → entryEdge → center → exitEdge
            //         (rovnako ako klasická priama dlaždica – cez stred)
            //
            //   2. ROHOVÝ PRECHOD – 45° turnout (kolmé smery):
            //         • Top    ↔ Right     • Top    ↔ Left
            //         • Bottom ↔ Right     • Bottom ↔ Left
            //      → entryEdge → innerEntry(t) → innerExit(t) → exitEdge
            //         (presne ten istý layout ako odbočka výhybky –
            //          stredný segment je 45° diagonálny, vstupný a výstupný
            //          segment idú pozdĺž osí príslušných hrán)
            //
            // Princíp je identický s odbočkou výhybky:
            //   • IBA priame čiarové segmenty (žiadne bezier krivky,
            //     žiadne splajny, žiadny smoothing).
            //   • Stred dlaždice sa NEPOUŽÍVA pre rohové prechody –
            //     pohyb sa stočí pred dosiahnutím stredu.
            //   • Tile-prechodové pravidlá zostávajú nezmenené:
            //     entryEdge a exitEdge sú štandardné hranové waypointy
            //     zhodné s ostatnými typmi.
            //
            // POZN: Crossroad detekcia musí prísť AŽ ZA switch detekciou,
            // pretože ako oddelenie používame počet bitov:
            //   curve = 2, switch = 3, crossroad = 4.
            // ────────────────────────────────────────────────────────────────
            if (!isFirst && !isLast && IsCrossroadTile(conns))
            {
                // ── ROHOVÝ PRECHOD: entry a exit sú kolmé smery ──────────
                if (IsPerpendicular(inDir, outDir))
                {
                    const float CROSSROAD_TURNOUT_T = 0.25f;
                    AddWaypoint(EdgePoint(tile, inDir), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, inDir, CROSSROAD_TURNOUT_T), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, outDir, CROSSROAD_TURNOUT_T), i);
                    AddWaypoint(EdgePoint(tile, outDir), i);
                    continue;
                }

                // ── PRIAMY PRECHOD: entry a exit sú proti-smery ─────────
                // Top↔Bottom alebo Left↔Right → klasický prejazd cez stred
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
                continue;
            }

            // ────────────────────────────────────────────────────────────────
            // PRIAMA / KONCOVÁ DLAŽDICA: edge → center → edge
            // ────────────────────────────────────────────────────────────────

            if (isFirst)
            {
                // Štart – začneme v centre
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
            }
            else if (isLast)
            {
                // Cieľ – vstúpime cez hranu a skončíme v centre
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
            }
            else
            {
                // Stredná priama dlaždica: entryEdge → center → exitEdge
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
            }
        }

        // Priradíme lokálne zoznamy do 'out' parametrov.
        waypoints = wp;
        tileIdxPerWaypoint = tIdx;
    }

    /// <summary>
    /// Vráti vrchol bunky pre krivku (vrchol, kde by sa dve hrany krivky
    /// stretli pri 90° lome). NEPOUŽÍVA sa v BuildWaypointPath – pohyb cez
    /// krivku je priamy Lerp medzi entryEdge a exitEdge (45° diagonála).
    /// Funkcia ostáva pre prípadné budúce vizuálne pomôcky / debug overlay.
    ///
    /// Pre RailCurveRightBottom (Right + Bottom):
    ///   Right hrana je x = tile.x+1, Bottom hrana je z = tile.z
    ///   → vrchol bunky = (tile.x+1, y, tile.z)
    /// </summary>
    Vector3 CurveCornerPoint(Vector2Int tile, IndicatrixAPI.DirectionMask conns)
    {
        float fx = tile.x, fz = tile.y;

        bool right = (conns & IndicatrixAPI.DirectionMask.Right) != 0;
        bool left = (conns & IndicatrixAPI.DirectionMask.Left) != 0;
        bool top = (conns & IndicatrixAPI.DirectionMask.Top) != 0;
        bool bottom = (conns & IndicatrixAPI.DirectionMask.Bottom) != 0;

        // X súradnica rohu: ak Right → fx+1, ak Left → fx
        float cx = right ? fx + 1.0f : fx + 0.0f;
        // Z súradnica rohu: ak Top → fz+1, ak Bottom → fz
        float cz = top ? fz + 1.0f : fz + 0.0f;

        // Y v rohu = priamo výška vertexu terénu v tom rohu (biliniárna
        // interpolácia v rohu sa zredukuje na hodnotu samotného rohového
        // vertexu). Korektné aj na svahu.
        float u = right ? 1f : 0f;
        float v = top ? 1f : 0f;
        float y = GetTileSurfaceY(tile, u, v) + TRACK_OFFSET_Y;

        return new Vector3(cx, y, cz);
    }

    // =====================================================================
    // (Pôvodný BuildPathData zachovaný pre spätnú kompatibilitu, ale už
    //  nepoužívaný v hlavnej ceste – ApplyNewPath používa BuildWaypointPath.)
    // =====================================================================

    PathData BuildPathData(List<Vector2Int> tilePath)
    {
        if (tilePath == null || tilePath.Count == 0) return null;
        BuildWaypointPath(tilePath, out var pts, out _);
        return pts.Count > 0 ? new PathData(pts) : null;
    }

    // =====================================================================
    // VIZUÁLNA SÚPRAVA – pomocná metóda vytvorenia
    // =====================================================================

    GameObject CreateConsistPart(string name, Color color, Vector3 position)
    {
        GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
        go.name = name;
        go.transform.localScale = CONSIST_SCALE;
        go.transform.position = position;
        Destroy(go.GetComponent<BoxCollider>());
        var rend = go.GetComponent<Renderer>();
        rend.material = new Material(Shader.Find("Standard"));
        rend.material.color = color;
        go.SetActive(false);
        return go;
    }

    // =====================================================================
    // SPRÁVA VLAKOV
    // =====================================================================

    /// <summary>
    /// Vytvorí vlak v depe [dx,dz].
    ///
    /// trainTypeIndex / wagonTypeIndex sú indexy zvolené v TrainTypeDropdown /
    /// WagonTypeDropdown (viď DepotRailConstructionMenuUI). Podľa nich sa
    /// z katalógov (TrainCatalog / WagonCatalog v TrainStock.cs) zostaví
    /// dátová štruktúra súpravy (TrainInstance) a uloží do td.consist.
    ///
    /// Všetky vagóny v súprave sú zatiaľ rovnakého zvoleného typu; ich počet
    /// je daný wagonCount. UI sa nemení – mapovanie atribútov na konkrétne UI
    /// prvky nie je potrebné, detailné okno si ich prečíta z td.consist neskôr.
    /// </summary>
    public bool CreateTrain(int dx, int dz, int trainTypeIndex, int wagonTypeIndex)
    {
        int key = DepotKey(dx, dz);
        if (trains.ContainsKey(key)) return false;
        if (GetTileID(dx, dz) != 3) return false;

        int count = Mathf.Clamp(wagonCount, 1, 10);
        TrainData td = new TrainData(dx, dz);
        Vector3 depotPos = TileCenter(new Vector2Int(dx, dz));

        // -----------------------------------------------------------------
        // DÁTOVÁ ŠTRUKTÚRA SÚPRAVY (TrainStock.cs)
        // -----------------------------------------------------------------
        TrainSpec trainSpec = TrainCatalog.ByIndex(trainTypeIndex);
        WagonSpec wagonSpec = WagonCatalog.ByIndex(wagonTypeIndex);

        if (trainSpec == null)
        {
            Debug.LogWarning($"[TrainSystem] Neznámy index typu vlaku ({trainTypeIndex}) – použijem prvý z katalógu.");
            trainSpec = TrainCatalog.ByIndex(0);
        }
        if (wagonSpec == null)
        {
            Debug.LogWarning($"[TrainSystem] Neznámy index typu vagónu ({wagonTypeIndex}) – použijem prvý z katalógu.");
            wagonSpec = WagonCatalog.ByIndex(0);
        }

        td.consist = new TrainInstance(trainSpec);
        for (int i = 0; i < count; i++)
            td.consist.Wagons.Add(new WagonInstance(wagonSpec));

        // Skrytá kocka – pohybová logika
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.localScale = CONSIST_SCALE;
        cube.transform.position = depotPos;
        cube.name = $"TrainHead_{dx}_{dz}";
        Destroy(cube.GetComponent<BoxCollider>());
        cube.GetComponent<Renderer>().enabled = false;
        td.trainObject = cube;

        // Lokomotíva – CYAN
        td.locomotive = CreateConsistPart($"Loco_{dx}_{dz}", new Color(0f, 1f, 1f), depotPos);

        // Vagóny – GREY
        for (int i = 0; i < count; i++)
        {
            var wagon = CreateConsistPart($"Wagon_{dx}_{dz}_{i}",
                new Color(0.502f, 0.502f, 0.502f), depotPos);
            td.wagons.Add(wagon);
        }

        trains[key] = td;
        Debug.Log($"[TrainSystem] Vlak '{td.consist.Name}' vytvorený v depe [{dx},{dz}] – "
                + $"1 lokomotíva + {count}× '{wagonSpec.Name}' (spacing={wagonSpacing}).");
        return true;
    }

    /// <summary>
    /// Spätne kompatibilný preťažený podpis – vytvorí vlak s prvým typom
    /// vlaku aj vagónu z katalógu (index 0). Volá sa z miest, kde voľba
    /// typu nie je k dispozícii (napr. klávesová skratka Q bez UI kontextu).
    /// </summary>
    public bool CreateTrain(int dx, int dz)
    {
        return CreateTrain(dx, dz, 0, 0);
    }

    public TrainData GetTrain(int dx, int dz)
    {
        int key = DepotKey(dx, dz);
        return trains.TryGetValue(key, out var td) ? td : null;
    }

    /// <summary>
    /// Vráti súradnice [depotX, depotZ] VŠETKÝCH aktuálne existujúcich
    /// vlakových dep. Keďže platí "1 train depo = 1 train", počet prvkov
    /// zoznamu = počet vlakov v hre.
    ///
    /// Slúži pre informačné UI (StatusStationsMenuUI). Vnútorný slovník
    /// `trains` ostáva privátny – navonok dávame len read-only kópiu
    /// kľúčových údajov, takže volajúci nemôže poškodiť interný stav.
    /// </summary>
    public List<Vector2Int> GetAllDepotCoords()
    {
        var result = new List<Vector2Int>(trains.Count);
        foreach (var kvp in trains)
        {
            TrainData td = kvp.Value;
            result.Add(new Vector2Int(td.depotX, td.depotZ));
        }
        return result;
    }

    /// <summary>
    /// Celkový počet vlakových dep (= počet vlakov). Pohodlný getter, aby
    /// UI nemuselo kvôli počtu vytvárať celý zoznam cez GetAllDepotCoords().
    /// </summary>
    public int GetDepotCount()
    {
        return trains.Count;
    }

    /// <summary>
    /// Vráti dátové štruktúry (TrainInstance) VŠETKÝCH aktuálne existujúcich
    /// vlakov. Keďže platí "1 train depo = 1 train", počet prvkov zoznamu =
    /// počet vlakov v hre.
    ///
    /// Slúži pre informačné UI (StatusTrainsMenuUI), ktoré z každej
    /// TrainInstance prečíta názov vlaku (Name), počet vagónov
    /// (Wagons.Count) a typ suroviny (Wagons[0].Type.Name).
    ///
    /// Vnútorný slovník `trains` ostáva privátny – navonok dávame len
    /// read-only kópiu zoznamu referencií na TrainInstance. Volajúci tak
    /// nemôže pridať/odobrať vlak (zmeniť `trains`), čítať atribúty
    /// jednotlivých vlakov ale môže. Analógia k GetAllDepotCoords().
    /// </summary>
    public List<TrainInstance> GetAllTrainConsists()
    {
        var result = new List<TrainInstance>(trains.Count);
        foreach (var kvp in trains)
        {
            TrainData td = kvp.Value;
            if (td != null && td.consist != null)
                result.Add(td.consist);
        }
        return result;
    }

    /// <summary>
    /// EKONOMIKA (read-only snapshot): pre KAŽDÝ existujúci vlak spáruje jeho
    /// DEPO súradnice [depotX, depotZ] (= kľúč pre <see cref="ReturnToDepot"/>)
    /// s jeho dátovou inštanciou <see cref="TrainInstance"/> (OperatingCosts,
    /// ServiceLife, ServicingInterval). Interný slovník `trains` ostáva privátny.
    ///
    /// Slúži pre EconomySystem: mesačné prevádzkové náklady + automatické
    /// poslanie vlaku do depa po uplynutí servisného intervalu alebo životnosti
    /// (EconomySystem zavolá <see cref="ReturnToDepot"/> s vrátenými súradnicami).
    /// </summary>
    public List<ActiveTrainInfo> GetActiveTrainInfos()
    {
        var result = new List<ActiveTrainInfo>(trains.Count);
        foreach (var kvp in trains)
        {
            TrainData td = kvp.Value;
            if (td != null && td.consist != null)
                result.Add(new ActiveTrainInfo(td.depotX, td.depotZ, td.consist));
        }
        return result;
    }

    /// <summary>Read-only dvojica [depo súradnice + dátová inštancia vlaku] pre EconomySystem.</summary>
    public readonly struct ActiveTrainInfo
    {
        public readonly int DepotX;
        public readonly int DepotZ;
        public readonly TrainInstance Consist;

        public ActiveTrainInfo(int depotX, int depotZ, TrainInstance consist)
        {
            DepotX = depotX;
            DepotZ = depotZ;
            Consist = consist;
        }
    }

    public bool AddStation(int depotX, int depotZ, int stX, int stZ)
    {
        TrainData td = GetTrain(depotX, depotZ);
        if (td == null) return false;
        if (GetTileID(stX, stZ) != 2) return false;

        Vector2Int st = new Vector2Int(stX, stZ);
        if (!td.stations.Contains(st))
        {
            td.stations.Add(st);
            Debug.Log($"[TrainSystem] Stanica [{stX},{stZ}] pridaná pre vlak v depe [{depotX},{depotZ}]. Celkom: {td.stations.Count}");
        }
        return true;
    }

    public bool StartTrain(int dx, int dz)
    {
        TrainData td = GetTrain(dx, dz);
        if (td == null) return false;
        if (td.stations.Count < 2)
        {
            Debug.LogWarning($"[TrainSystem] Vlak v depe [{dx},{dz}] nemá aspoň 2 stanice!");
            return false;
        }

        bool isResume = !td.isAtDepot && td.currentPath != null && td.currentPath.Count > 0;

        td.isRunning = true;
        td.isWaiting = false;
        td.isReturningToDepot = false;

        if (isResume)
        {
            Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] OBNOVENÝ (resume).");
        }
        else
        {
            td.isAtDepot = false;
            td.currentStationIndex = 0;
            td.reverseDirection = false;
            td.moveTimer = 0f;
            td.waitTimer = 0f;
            td.pendingPath = null;
            td.pendingReturnToDepot = false;

            // Reset path state
            td.activePath = null;
            td.locomotiveDistance = 0f;

            ComputeNextPathAsync(td);
            Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] SPUSTENÝ (prvý štart).");
        }

        return true;
    }

    public bool StopTrain(int dx, int dz)
    {
        TrainData td = GetTrain(dx, dz);
        if (td == null) return false;
        td.isRunning = false;
        td.isComputingPath = false;
        td.pendingPath = null;
        td.pendingReturnToDepot = false;
        Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] ZASTAVENÝ.");
        return true;
    }

    public ReturnToDepotResult ReturnToDepot(int dx, int dz)
    {
        TrainData td = GetTrain(dx, dz);
        if (td == null) return ReturnToDepotResult.Error;

        if (GetTileID(dx, dz) != 3)
        {
            Debug.LogWarning($"[TrainSystem] Depo [{dx},{dz}] neexistuje – vlak zastane.");
            td.isRunning = false;
            td.isStoppedAwaitingDepotReturn = false;
            return ReturnToDepotResult.Error;
        }

        if (td.isStoppedAwaitingDepotReturn)
        {
            td.isStoppedAwaitingDepotReturn = false;
            td.pendingReturnToDepot = false;
            td.isRunning = true;
            td.isWaiting = false;
            td.waitTimer = 0f;
            td.isGoingToDepotViaStation = false;
            td.isComputingPath = false;
            td.pendingPath = null;
            DispatchToDepot(td);
            Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] → depo priamo (R po zastavení bez staníc).");
            return ReturnToDepotResult.Dispatched;
        }

        Vector2Int depotTile = new Vector2Int(dx, dz);

        if (td.isWaiting)
        {
            td.pendingReturnToDepot = false;
            td.isGoingToDepotViaStation = true;
            td.isStoppedAwaitingDepotReturn = false;
            Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] čaká na stanici → po odpočítaní pôjde do depa.");
            return ReturnToDepotResult.Dispatched;
        }

        if (IsDepotOnCurrentPath(td, depotTile))
        {
            td.pendingReturnToDepot = false;
            td.isGoingToDepotViaStation = false;
            td.isStoppedAwaitingDepotReturn = false;
            TrimPathToDepot(td, depotTile);
            td.isReturningToDepot = true;
            // Prebuduj PathData pre skrátenú cestu
            RebuildActivePathFromCurrentPath(td);
            Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] → depo PRIAMO (depo je na aktuálnej ceste).");
            return ReturnToDepotResult.Dispatched;
        }

        td.pendingReturnToDepot = true;
        td.isGoingToDepotViaStation = false;
        td.isStoppedAwaitingDepotReturn = false;
        Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}]: depo nie je v smere jazdy → vlak dokončí cestu na plánovanú stanicu a potom pôjde do depa.");
        return ReturnToDepotResult.Dispatched;
    }

    bool IsDepotOnCurrentPath(TrainData td, Vector2Int depotTile)
    {
        if (td.currentPath == null || td.pathIndex >= td.currentPath.Count) return false;
        for (int i = td.pathIndex; i < td.currentPath.Count; i++)
            if (td.currentPath[i] == depotTile) return true;
        return false;
    }

    void TrimPathToDepot(TrainData td, Vector2Int depotTile)
    {
        if (td.currentPath == null) return;
        for (int i = td.pathIndex; i < td.currentPath.Count; i++)
        {
            if (td.currentPath[i] == depotTile)
            {
                td.currentPath = td.currentPath.GetRange(0, i + 1);
                return;
            }
        }
    }

    void DispatchToDepot(TrainData td)
    {
        int key = DepotKey(td.depotX, td.depotZ);
        Vector2Int goal = new Vector2Int(td.depotX, td.depotZ);
        Vector2Int start = td.currentTile;
        var snap = SnapshotTileGrid();

        td.isReturningToDepot = true;
        td.isGoingToDepotViaStation = false;
        td.isComputingPath = true;

        Task.Run(() =>
        {
            var path = AStarPathThreaded(snap, start, goal);
            _pendingPathResults.Enqueue(new PathResult
            {
                depotKey = key,
                path = path,
                isReturnToDepot = true,
                destination = PathDestination.Depot
            });
        });

        Debug.Log($"[TrainSystem] Vlak z depa [{td.depotX},{td.depotZ}] → depo.");
    }

    /// <summary>
    /// Odstráni vlak z hry. Vymazanie je povolené VÝLUČNE vtedy, keď je
    /// vlak fyzicky v depe (currentTile == depot tile) a nie je v pohybe
    /// (isRunning == false). Flag isAtDepot sa NEPOUŽÍVA – v praxi sa
    /// dostal mimo synchronizáciu pri scenári, keď je vlak po stlačení S
    /// zaregistrovaný ako "vyšiel z depa" (isAtDepot = false), ale A*
    /// nenašiel cestu von, takže vlak zostal fyzicky stáť na depote.
    /// Po následnom P (StopTrain) by stary guard zablokoval T (RemoveTrain),
    /// hoci vlak depo nikdy neopustil.
    ///
    /// Nový guard kontroluje:
    ///   1. Vlak v evidencii existuje (td != null).
    ///   2. Vlak nie je v pohybe (!td.isRunning).
    ///   3. Vlak je fyzicky na tile depota (td.currentTile == (dx, dz)).
    ///   4. Tile (dx, dz) je stále depo (GetTileID == 3).
    ///
    /// Vyrieši oba scenáre používateľa:
    ///   • Q (CreateTrain) → T (RemoveTrain) bezprostredne:
    ///     vlak ešte nikdy nevyšiel, currentTile = (dx,dz), isRunning = false
    ///     → povolené.
    ///   • S (StartTrain) → A* nenájde cestu → P (StopTrain) → T:
    ///     vlak depo neopustil, currentTile = (dx,dz), isRunning = false
    ///     → povolené.
    /// V iných stavoch (vlak na trati, hoci aj zastavený) vymazanie
    /// zlyhá, čo je v súlade s požiadavkou.
    /// </summary>
    public bool RemoveTrain(int dx, int dz)
    {
        TrainData td = GetTrain(dx, dz);
        if (td == null) return false;

        if (td.isRunning)
        {
            Debug.LogWarning($"[TrainSystem] Vlak v depe [{dx},{dz}] sa nedá odstrániť – stále beží. Najprv ho zastavte (P).");
            return false;
        }

        Vector2Int depotTile = new Vector2Int(dx, dz);
        if (td.currentTile != depotTile)
        {
            Debug.LogWarning($"[TrainSystem] Vlak v depe [{dx},{dz}] sa nedá odstrániť – nie je fyzicky v depe (aktuálne na [{td.currentTile.x},{td.currentTile.y}]).");
            return false;
        }

        if (GetTileID(dx, dz) != 3)
        {
            Debug.LogWarning($"[TrainSystem] Tile [{dx},{dz}] už nie je depo – vlak sa nedá odstrániť.");
            return false;
        }

        // Defenzívne: ak by ešte stále prebiehal asynchrónny A* výpočet,
        // jeho výsledok bude zahodený (TryGetValue v ApplyPendingPathResults
        // neuspeje, lebo sme záznam už odstránili z trains).
        td.isComputingPath = false;
        td.pendingPath = null;

        // Uvoľníme VŠETKY štruktúry, ktoré vytvoril CreateTrain – symetricky.
        DestroyTrainStructures(td);

        trains.Remove(DepotKey(dx, dz));
        Debug.Log($"[TrainSystem] Vlak z depa [{dx},{dz}] ODSTRÁNENÝ.");
        return true;
    }

    /// <summary>
    /// Uvoľní všetky štruktúry vlakovej súpravy, ktoré boli vytvorené
    /// v CreateTrain. Táto metóda je presným ZRKADLOM CreateTrain –
    /// pre každú štruktúru vytvorenú pri vytvorení vlaku tu existuje
    /// zodpovedajúce zrušenie:
    ///
    ///   CreateTrain vytvorí          →  DestroyTrainStructures uvoľní
    ///   ─────────────────────────────────────────────────────────────
    ///   td.trainObject (GameObject)   →  Destroy(td.trainObject)
    ///   td.locomotive  (GameObject)   →  Destroy(td.locomotive)
    ///   td.wagons      (GameObjecty)  →  Destroy(každý) + zoznam vyčistený
    ///   td.consist     (TrainInstance →  Wagons vyčistené + referencia null
    ///                  + WagonInstance)
    ///
    /// Týmto je zaručené, že ak sa pri vytvorení vlaku alokuje nejaká
    /// štruktúra, tá istá štruktúra sa pri odstránení vlaku aj korektne
    /// uvoľní – žiadne "visiace" GameObjecty ani mŕtve referencie
    /// v td.wagons po Destroy().
    /// </summary>
    void DestroyTrainStructures(TrainData td)
    {
        if (td == null) return;

        // Skrytá kocka – pohybová logika (zrkadlí: cube → td.trainObject).
        if (td.trainObject != null) Destroy(td.trainObject);
        td.trainObject = null;

        // Vizuálna lokomotíva (zrkadlí: CreateConsistPart → td.locomotive).
        if (td.locomotive != null) Destroy(td.locomotive);
        td.locomotive = null;

        // Vizuálne vagóny (zrkadlí: CreateConsistPart slučka → td.wagons).
        if (td.wagons != null)
        {
            foreach (var w in td.wagons)
                if (w != null) Destroy(w);
            // Vyčistíme zoznam, aby v td.wagons neostali mŕtve referencie
            // na už zničené GameObjecty.
            td.wagons.Clear();
        }

        // Dátová štruktúra súpravy (zrkadlí: new TrainInstance + WagonInstance
        // slučka → td.consist). Uvoľníme vagóny a samotnú referenciu.
        if (td.consist != null)
        {
            td.consist.Wagons?.Clear();
            td.consist = null;
        }

        // Vyčistíme aj pohybový / pathfinding stav, aby objekt TrainData
        // neostal v nekonzistentnom stave, ak naň ešte drží referenciu
        // prebiehajúci asynchrónny výpočet.
        td.activePath = null;
        td.currentPath = null;
        td.pendingPath = null;
    }

    // =====================================================================
    // NOTIFIKÁCIA PRI ZMENE MAPY – DEBOUNCE
    // =====================================================================

    public void OnMapChanged()
    {
        _mapChangePending = true;
        _mapChangedDebounceTimer = DEBOUNCE_DELAY;
    }

    // =====================================================================
    // UPDATE
    // =====================================================================

    void Update()
    {
        if (_mapChangePending)
        {
            _mapChangedDebounceTimer -= Time.deltaTime;
            if (_mapChangedDebounceTimer <= 0f)
            {
                _mapChangePending = false;
                _mapChangedDebounceTimer = 0f;
                TriggerMapChangedReroute();
            }
        }

        ApplyPendingPathResults();

        foreach (var kvp in trains)
        {
            TrainData td = kvp.Value;
            if (td.trainObject == null) continue;
            if (!td.isRunning) continue;
            UpdateTrain(td);
        }
    }

    // =====================================================================
    // POHYB VLAKU – DISTANCE-BASED
    // =====================================================================

    void UpdateTrain(TrainData td)
    {
        // ── Čakanie (stanica / pauza) ─────────────────────────────────────
        if (td.isWaiting)
        {
            td.waitTimer -= Time.deltaTime;

            // ── OBCHOD: po 2 s čakania na cieľovej stanici ────────────────
            // Spustí sa práve raz za zastávku. tradeDoneAtStation je nastavené
            // na false iba pri príchode na cieľovú stanicu (OnPathComplete),
            // takže pri BREAK_WAIT ani pri ceste do depa sa obchod nespustí.
            if (!td.tradeDoneAtStation
                && (STATION_WAIT - td.waitTimer) >= TRADE_DELAY)
            {
                td.tradeDoneAtStation = true;
                TryExecuteTradeAtStation(td);
            }

            if (td.waitTimer <= 0f)
            {
                td.isWaiting = false;
                td.waitTimer = 0f;

                if (td.isReturningToDepot)
                    DispatchToDepot(td);
                else if (td.isGoingToDepotViaStation)
                    DispatchToDepot(td);
                else
                    AdvanceStation(td);
            }
            return;
        }

        // ── Čakáme na výpočet novej trasy ────────────────────────────────
        if (td.activePath == null)
        {
            if (td.isComputingPath) return;
            // Ak niet aktívnej trasy a nie sme počítajú, skúsime spustiť prepočet
            OnPathComplete(td);
            return;
        }

        // ── Pohyb lokomotívy pozdĺž trasy ────────────────────────────────
        td.locomotiveDistance += td.trainSpeed * Time.deltaTime;

        // Aktualizujeme currentTile podľa najbližšieho bodu na trase
        UpdateCurrentTile(td);

        // Pohyb vizuálnej hlavy (skrytá kocka)
        Vector3 headPos = td.activePath.GetPositionAtDistance(td.locomotiveDistance);
        td.trainObject.transform.position = headPos;

        // ── Aktualizácia vizuálnej súpravy ───────────────────────────────
        UpdateConsistVisuals(td);

        // ── Detekcia konca trasy ──────────────────────────────────────────
        if (td.locomotiveDistance >= td.activePath.totalLength)
        {
            // Lokomotíva dorazila na koniec – zafixujeme na posledný bod
            td.locomotiveDistance = td.activePath.totalLength;
            td.currentTile = td.currentPath != null && td.currentPath.Count > 0
                ? td.currentPath[td.currentPath.Count - 1]
                : td.currentTile;

            // Aplicujeme pendingPath ak existuje
            if (td.pendingPath != null)
            {
                List<Vector2Int> pending = td.pendingPath;
                td.pendingPath = null;
                ApplyNewPath(td, pending);
                return;
            }

            td.activePath = null;
            OnPathComplete(td);
        }
    }

    /// <summary>
    /// Udržiava currentTile synchronizované s aktuálnou pozíciou lokomotívy na trase.
    /// Použije waypointToTileIdx mapovanie pre presné určenie aktuálnej dlaždice
    /// pri waypoint-based pohybe.
    /// </summary>
    void UpdateCurrentTile(TrainData td)
    {
        if (td.currentPath == null || td.activePath == null) return;
        if (td.waypointToTileIdx == null || td.waypointToTileIdx.Length == 0) return;

        // Nájdeme index waypointu, kde sa lokomotíva práve nachádza
        float[] cumLens = td.activePath.cumulativeLengths;
        int lastPassedIdx = 0;
        for (int i = 0; i < cumLens.Length; i++)
        {
            if (cumLens[i] <= td.locomotiveDistance)
                lastPassedIdx = i;
            else
                break;
        }

        // Mapuj waypoint-index na sub-path tile-index, potom na currentPath-index
        int subPathTileIdx = td.waypointToTileIdx[lastPassedIdx];
        int tileIdx = td.pathIndexAtPathStart + subPathTileIdx;
        if (tileIdx >= 0 && tileIdx < td.currentPath.Count)
            td.currentTile = td.currentPath[tileIdx];
    }

    // =====================================================================
    // VIZUÁLNA SÚPRAVA – DISTANCE-BASED
    // =====================================================================

    /// <summary>
    /// Aktualizuje pozíciu, rotáciu a viditeľnosť každého člena súpravy.
    ///
    /// Člen i:
    ///   offsetDist = i * wagonSpacing
    ///   memberDist = locomotiveDistance - offsetDist  (min 0)
    ///   pos = activePath.GetPositionAtDistance(memberDist)
    ///   rot = Quaternion.LookRotation(activePath.GetDirectionAtDistance(memberDist))
    ///
    /// Zobrazenie: člen i sa zobrazí keď locomotiveDistance >= i * wagonSpacing.
    /// Skrývanie pri návrate do depa: keď memberDist >= totalLength - DEPOT_HIDE_MARGIN.
    /// </summary>
    void UpdateConsistVisuals(TrainData td)
    {
        if (td.activePath == null) return;

        const float DEPOT_HIDE_RADIUS = 0.55f;
        bool isReturning = td.isReturningToDepot;
        Vector3 depotCenter = TileCenter(new Vector2Int(td.depotX, td.depotZ));
        int totalMembers = 1 + td.wagons.Count;

        for (int i = 0; i < totalMembers; i++)
        {
            float offsetDist = i * wagonSpacing;
            GameObject member = (i == 0) ? td.locomotive : td.wagons[i - 1];
            if (member == null) continue;

            // ── Kritérium zobrazenia ───────────────────────────────────────
            bool shouldBeVisible = td.locomotiveDistance >= offsetDist;

            float memberDist = Mathf.Max(0f, td.locomotiveDistance - offsetDist);

            // ── Skrývanie pri návrate do depa ─────────────────────────────
            if (isReturning && shouldBeVisible)
            {
                Vector3 memberPos = td.activePath.GetPositionAtDistance(memberDist);
                if (Vector3.Distance(memberPos, depotCenter) < DEPOT_HIDE_RADIUS)
                    shouldBeVisible = false;
            }

            if (member.activeSelf != shouldBeVisible)
                member.SetActive(shouldBeVisible);

            if (!shouldBeVisible) continue;

            // ── Pozícia ───────────────────────────────────────────────────
            Vector3 pos = td.activePath.GetPositionAtDistance(memberDist);

            if (float.IsNaN(pos.x) || float.IsNaN(pos.y) || float.IsNaN(pos.z))
            {
                Debug.LogWarning($"[TrainSystem] NaN pozícia pre člen {i} – preskočené.");
                continue;
            }

            // ── Rotácia – priamo zo smeru segmentu, bez akumulácie ────────
            Vector3 dir = td.activePath.GetDirectionAtDistance(memberDist);
            Quaternion rot = Quaternion.LookRotation(dir, Vector3.up);

            member.transform.position = pos;
            member.transform.rotation = rot;
        }
    }

    // =====================================================================
    // DOKONČENIE CESTY
    // =====================================================================

    void OnPathComplete(TrainData td)
    {
        if (td.isReturningToDepot)
        {
            td.isRunning = false;
            td.isAtDepot = true;
            td.isReturningToDepot = false;
            td.isGoingToDepotViaStation = false;
            td.currentTile = new Vector2Int(td.depotX, td.depotZ);
            td.trainObject.transform.position = TileCenter(td.currentTile);
            td.activePath = null;
            td.locomotiveDistance = 0f;

            HideAllConsist(td);
            Debug.Log($"[TrainSystem] Vlak z depa [{td.depotX},{td.depotZ}] dorazil do DEPA – súprava skrytá.");
            return;
        }

        if (td.isGoingToDepotViaStation)
        {
            bool onStation = td.stations.Contains(td.currentTile)
                             && GetTileID(td.currentTile.x, td.currentTile.y) == 2;
            if (onStation)
            {
                Debug.Log($"[TrainSystem] Vlak dorazil na stanicu [{td.currentTile.x},{td.currentTile.y}] pred depom – čaká {STATION_WAIT}s.");
                td.trainObject.transform.position = TileCenter(td.currentTile);
                td.isWaiting = true;
                td.waitTimer = STATION_WAIT;
                // Zastávka pred návratom do depa – obchod sa nerealizuje.
                td.tradeDoneAtStation = true;
            }
            else
            {
                Debug.LogWarning($"[TrainSystem] Vlak nedosiahol stanicu pred depom. Zastane.");
                td.isRunning = false;
                td.isGoingToDepotViaStation = false;
            }
            return;
        }

        Vector2Int targetStation = td.stations[td.currentStationIndex];
        if (td.currentTile == targetStation)
        {
            if (td.pendingReturnToDepot)
            {
                td.pendingReturnToDepot = false;
                td.isGoingToDepotViaStation = true;
                Debug.Log($"[TrainSystem] Vlak dorazil na stanicu [{targetStation.x},{targetStation.y}] (pending návrat do depa) – čaká {STATION_WAIT}s, potom depo.");
                // Posledná zastávka pred depom – obchod sa nerealizuje.
                td.tradeDoneAtStation = true;
            }
            else
            {
                Debug.Log($"[TrainSystem] Vlak dorazil na stanicu [{targetStation.x},{targetStation.y}] – čaká {STATION_WAIT}s.");
                // Riadna zastávka na cieľovej stanici – po 2 s prebehne obchod.
                td.tradeDoneAtStation = false;
            }
            td.trainObject.transform.position = TileCenter(td.currentTile);
            td.isWaiting = true;
            td.waitTimer = STATION_WAIT;
        }
        else
        {
            Debug.LogWarning($"[TrainSystem] Vlak nedosiahol stanicu [{targetStation.x},{targetStation.y}]. Čaká {BREAK_WAIT}s.");
            td.isWaiting = true;
            td.waitTimer = BREAK_WAIT;
            // Núdzové prerušenie – nie je to zastávka na stanici, žiadny obchod.
            td.tradeDoneAtStation = true;
        }
    }

    // =====================================================================
    // OBCHOD NA STANICI – TRADE (TradeSystem)
    // =====================================================================

    /// <summary>
    /// Vykoná výmenu tovaru medzi súpravou vlaku a továrňami stanice, na
    /// ktorej vlak práve čaká. Volá sa z UpdateTrain po 2 s čakania
    /// (TRADE_DELAY), práve raz za zastávku.
    ///
    /// Postup:
    ///   1) Zisti tile, na ktorom vlak stojí (cieľová stanica).
    ///   2) Nájdi StationInstance pre tento tile v StationRegistry.
    ///      Ak ešte nie je zaregistrovaná (napr. stanica pribudla bez
    ///      následného scanu), dorovná sa cez RescanAll.
    ///   3) Odovzdaj súpravu + stanicu do TradeSystem.Execute.
    ///   4) Podľa výsledku vypíš Debug.Log (úspech / neúspech).
    ///
    /// Výpisy presne zodpovedajú zadaniu:
    ///   úspech  → "Predaj prebehol úspešne, cena {N} euro"
    ///   neúspech→ "Obchod neprebehol."
    /// </summary>
    void TryExecuteTradeAtStation(TrainData td)
    {
        // Bezpečnostné kontroly – bez dátovej súpravy nemá obchod zmysel.
        if (td == null || td.consist == null)
        {
            Debug.Log("Obchod neprebehol.");
            return;
        }

        Vector2Int stationTile = td.currentTile;

        // Nájdi stanicu v registri. Ak chýba, skús dorovnať register.
        StationInstance station = RailStationRegistry.GetStationAt(stationTile.x, stationTile.y);
        if (station == null)
        {
            RailStationRegistry.RescanAll();
            station = RailStationRegistry.GetStationAt(stationTile.x, stationTile.y);
        }

        if (station == null)
        {
            Debug.Log("Obchod neprebehol.");
            Debug.LogWarning($"[TrainSystem] Pre tile [{stationTile.x},{stationTile.y}] " +
                             $"sa nenašla StationInstance – obchod sa neuskutočnil.");
            return;
        }

        // Vykonaj samotnú transakciu (čisto dátová operácia).
        TrainTradeSystem.TradeResult result = TrainTradeSystem.Execute(td.consist, station);

        if (result.Success)
        {
            // Pripíš zárobok na herné konto – preprava tovaru je jediný zdroj
            // príjmu, ktorý drží konto v pluse (a umožní reset cenového
            // násobiteľa po prepadnutí do mínusu).
            if (result.Revenue > 0)
            {
                if (GameEconomy.instance != null)
                    GameEconomy.instance.AddCredits((uint)result.Revenue);

                // EVIDENCIA pre ročnú uzávierku – tržba z úspešnej prepravy
                // (jediný zdroj príjmu hry, rovnako pre vlaky aj vozidlá).
                BudgetSystem.instance?.RecordRevenue(result.Revenue);
            }

            Debug.Log($"Obchodná transakcia bola vykonaná úspešne, suma {result.Revenue} CR.");
            Debug.Log($"[TrainSystem] Obchod na stanici [{stationTile.x},{stationTile.y}]: " +
                      $"naložené {result.LoadedUnits}, vyložené {result.UnloadedUnits}, " +
                      $"zárobok {result.Revenue} CR.");

            // Plávajúci cenový label nad vlakom na 3 s. Volá sa presne tu –
            // teda po 2 s od príchodu na stanicu (keď prebehne transakcia).
            if (td.locomotive != null)
                SalePriceLabelManager.Instance.ShowPrice(
                    td.locomotive.transform.position, result.Revenue);
        }
        else
        {
            Debug.Log("Obchod neprebehol.");
            Debug.Log($"[TrainSystem] Obchod na stanici [{stationTile.x},{stationTile.y}] " +
                      $"neprebehol – dôvod: {result.Reason}.");
        }
    }

    void HideAllConsist(TrainData td)
    {
        if (td.locomotive != null) td.locomotive.SetActive(false);
        foreach (var w in td.wagons)
            if (w != null) w.SetActive(false);
    }

    // =====================================================================
    // ADVANCE STATION – OBRAT SMERU
    // =====================================================================

    void AdvanceStation(TrainData td)
    {
        if (!td.reverseDirection)
        {
            td.currentStationIndex++;
            if (td.currentStationIndex >= td.stations.Count)
            {
                td.reverseDirection = true;
                td.currentStationIndex = td.stations.Count - 2;
                if (td.currentStationIndex < 0) td.currentStationIndex = 0;
            }
        }
        else
        {
            td.currentStationIndex--;
            if (td.currentStationIndex < 0)
            {
                td.reverseDirection = false;
                td.currentStationIndex = 1;
                if (td.currentStationIndex >= td.stations.Count)
                    td.currentStationIndex = 0;
            }
        }

        ComputeNextPathAsync(td);
    }

    // =====================================================================
    // ASYNC A* WRAPPER
    // =====================================================================

    void ComputeNextPathAsync(TrainData td)
    {
        if (td.stations.Count == 0) return;
        if (td.isComputingPath) return;

        Vector2Int goal = td.stations[td.currentStationIndex];
        Vector2Int start = td.currentTile;
        int key = DepotKey(td.depotX, td.depotZ);
        var snap = SnapshotTileGrid();

        td.isComputingPath = true;

        Task.Run(() =>
        {
            var path = AStarPathThreaded(snap, start, goal);
            _pendingPathResults.Enqueue(new PathResult
            {
                depotKey = key,
                path = path,
                isReturnToDepot = false
            });
        });
    }

    // =====================================================================
    // TRIGGER MAP CHANGED REROUTE
    // =====================================================================

    void TriggerMapChangedReroute()
    {
        // Mapa sa zmenila (pribudli/ubudli koľaje, stanice alebo továrne) –
        // prepočítaj 9×9 zóny staníc a ich evidenciu tovární. Lacná operácia.
        RailStationRegistry.RescanAll();

        var snap = SnapshotTileGrid();

        foreach (var kvp in trains)
        {
            TrainData td = kvp.Value;
            if (!td.isRunning) continue;
            if (td.isComputingPath) continue;

            int key = kvp.Key;

            if (td.isReturningToDepot)
            {
                Vector2Int start = td.currentTile;
                Vector2Int goal = new Vector2Int(td.depotX, td.depotZ);
                td.isComputingPath = true;
                Task.Run(() =>
                {
                    var path = AStarPathThreaded(snap, start, goal);
                    _pendingPathResults.Enqueue(new PathResult
                    {
                        depotKey = key,
                        path = path,
                        isReturnToDepot = true,
                        destination = PathDestination.Depot
                    });
                });
            }
            else if (td.isGoingToDepotViaStation)
            {
                Vector2Int? nearest = FindNearestStation(td);
                if (!nearest.HasValue) continue;
                Vector2Int start = td.currentTile;
                Vector2Int goal = nearest.Value;
                td.isComputingPath = true;
                Task.Run(() =>
                {
                    var path = AStarPathThreaded(snap, start, goal);
                    _pendingPathResults.Enqueue(new PathResult
                    {
                        depotKey = key,
                        path = path,
                        isReturnToDepot = false,
                        destination = PathDestination.ReturnViaStation
                    });
                });
            }
            else
            {
                if (td.stations.Count == 0) continue;
                Vector2Int start = td.currentTile;
                Vector2Int goal = td.stations[td.currentStationIndex];
                td.isComputingPath = true;
                Task.Run(() =>
                {
                    var path = AStarPathThreaded(snap, start, goal);
                    _pendingPathResults.Enqueue(new PathResult
                    {
                        depotKey = key,
                        path = path,
                        isReturnToDepot = false,
                        destination = PathDestination.Station
                    });
                });
            }
        }
    }

    // =====================================================================
    // POMOCNÁ – Nearest Station
    // =====================================================================

    Vector2Int? FindNearestStation(TrainData td)
    {
        Vector2Int? nearest = null;
        int bestDist = int.MaxValue;
        foreach (var st in td.stations)
        {
            if (GetTileID(st.x, st.y) != 2) continue;
            int dist = Math.Abs(td.currentTile.x - st.x) + Math.Abs(td.currentTile.y - st.y);
            if (dist < bestDist) { bestDist = dist; nearest = st; }
        }
        return nearest;
    }

    // =====================================================================
    // APLIKÁCIA VÝSLEDKOV A*
    // =====================================================================

    void ApplyPendingPathResults()
    {
        while (_pendingPathResults.TryDequeue(out PathResult result))
        {
            if (!trains.TryGetValue(result.depotKey, out TrainData td)) continue;

            td.isComputingPath = false;
            bool hasPath = result.path != null && result.path.Count > 0;

            if (result.destination == PathDestination.Depot)
            {
                if (hasPath) { ApplyNewPath(td, result.path); td.isWaiting = false; }
                else
                {
                    td.isRunning = false; td.isWaiting = true; td.waitTimer = BREAK_WAIT;
                    Debug.LogWarning($"[TrainSystem] Vlak z depa [{td.depotX},{td.depotZ}] nemôže nájsť cestu do depa, čaká.");
                }
                continue;
            }

            if (result.destination == PathDestination.ReturnViaStation)
            {
                if (hasPath) { ApplyNewPath(td, result.path); td.isWaiting = false; }
                else
                {
                    Debug.LogWarning($"[TrainSystem] Vlak z depa [{td.depotX},{td.depotZ}] nemôže nájsť cestu na stanicu. Zastane.");
                    td.isRunning = false; td.isWaiting = false; td.isGoingToDepotViaStation = false;
                }
                continue;
            }

            if (result.isReturnToDepot)
            {
                if (hasPath) { ApplyNewPath(td, result.path); td.isWaiting = false; }
                else
                {
                    td.isRunning = false; td.isWaiting = true; td.waitTimer = BREAK_WAIT;
                    Debug.LogWarning($"[TrainSystem] Vlak nemôže nájsť cestu do depa, čaká.");
                }
            }
            else
            {
                if (hasPath) { ApplyNewPath(td, result.path); td.isWaiting = false; }
                else
                {
                    bool anyStationExists = false;
                    foreach (var st in td.stations)
                        if (GetTileID(st.x, st.y) == 2) { anyStationExists = true; break; }

                    if (!anyStationExists)
                    {
                        Debug.Log($"[TrainSystem] Vlak z depa [{td.depotX},{td.depotZ}]: žiadne dostupné stanice – vlak zastane. Stlačte R pre návrat do depa.");
                        td.isRunning = false; td.isWaiting = false;
                        td.isStoppedAwaitingDepotReturn = true;
                        td.currentPath = new List<Vector2Int>(); td.pathIndex = 0;
                        td.activePath = null;

                        // Synchronizácia flag-u isAtDepot s reálnou polohou.
                        // StartTrain (ne-resume) ho už nastavil na false, ale
                        // vlak fyzicky depo neopustil → vraciame flag späť na
                        // true, aby zodpovedal skutočnosti.
                        if (td.currentTile.x == td.depotX && td.currentTile.y == td.depotZ)
                            td.isAtDepot = true;
                    }
                    else
                    {
                        if (td.stations.Count > 0)
                        {
                            var goal = td.stations[td.currentStationIndex];
                            Debug.LogWarning($"[TrainSystem] A* nenašiel cestu do [{goal.x},{goal.y}]. Čakám {BREAK_WAIT}s.");
                        }
                        td.isWaiting = true; td.waitTimer = BREAK_WAIT;
                        td.currentPath = new List<Vector2Int>(); td.pathIndex = 0;
                        td.activePath = null;

                        // Synchronizácia flag-u isAtDepot s reálnou polohou.
                        // Vlak po neúspešnom A* zostáva fyzicky na depe –
                        // flag musí zodpovedať tomuto stavu, aby StopTrain (P)
                        // a následný RemoveTrain (T) fungovali korektne.
                        if (td.currentTile.x == td.depotX && td.currentTile.y == td.depotZ)
                            td.isAtDepot = true;
                    }
                }
            }
        }
    }

    // =====================================================================
    // APPLY NEW PATH – konvertuje List<Vector2Int> na PathData (waypoint-based)
    // =====================================================================

    void ApplyNewPath(TrainData td, List<Vector2Int> newPath)
    {
        bool isMoving = td.activePath != null && td.locomotiveDistance < td.activePath.totalLength;

        // Ak vlak práve prechádza medzi dvoma dlaždicami, aplikujeme nový
        // path od dlaždice, na ktorej sa aktuálne nachádza
        Vector2Int startTile = td.currentTile;
        int startIdxInNew = -1;
        for (int i = 0; i < newPath.Count; i++)
        {
            if (newPath[i] == startTile) { startIdxInNew = i; break; }
        }

        if (startIdxInNew < 0)
        {
            // Vlak nie je na novej ceste – odložíme na neskôr alebo aplikujeme od začiatku
            if (isMoving)
            {
                td.pendingPath = newPath;
                return;
            }
            startIdxInNew = 0;
        }

        // Subpath od aktuálneho tielu po koniec
        List<Vector2Int> subPath = newPath.GetRange(startIdxInNew, newPath.Count - startIdxInNew);

        td.currentPath = newPath;
        td.pathIndex = startIdxInNew;
        td.pathIndexAtPathStart = startIdxInNew;
        td.moveTimer = 0f;

        // Zostav PathData – WAYPOINT-BASED (centrá + hrany + krivkové rohy)
        BuildWaypointPath(subPath, out var pts, out var tileIdxList);
        var newPathData = new PathData(pts);

        // Rýchlosť: 1 dlaždica / MOVE_TIME s.
        // Pri waypoint-based ceste obsahuje newPathData viac segmentov ako len
        // počet dlaždíc – upravíme rýchlosť tak, aby vlak prešiel celú cestu
        // za rovnaký čas ako pri pôvodnom center-only systéme.
        float expectedTime = (subPath.Count - 1) * MOVE_TIME;
        td.trainSpeed = (newPathData.totalLength > 0f && expectedTime > 0f)
            ? newPathData.totalLength / expectedTime
            : 1f / MOVE_TIME;

        td.locomotiveDistance = 0f;
        td.activePath = newPathData;
        td.waypointToTileIdx = tileIdxList.ToArray();
    }

    /// <summary>
    /// Prebuduje activePath z currentPath (po TrimPathToDepot).
    /// </summary>
    void RebuildActivePathFromCurrentPath(TrainData td)
    {
        if (td.currentPath == null || td.currentPath.Count == 0)
        {
            td.activePath = null;
            return;
        }

        int startIdx = td.pathIndex;
        if (startIdx >= td.currentPath.Count) { td.activePath = null; return; }

        List<Vector2Int> sub = td.currentPath.GetRange(startIdx, td.currentPath.Count - startIdx);

        BuildWaypointPath(sub, out var pts, out var tileIdxList);
        var pd = new PathData(pts);

        td.pathIndexAtPathStart = startIdx;
        float expectedTime = (sub.Count - 1) * MOVE_TIME;
        td.trainSpeed = (pd.totalLength > 0f && expectedTime > 0f)
            ? pd.totalLength / expectedTime
            : 1f / MOVE_TIME;
        td.locomotiveDistance = 0f;
        td.activePath = pd;
        td.waypointToTileIdx = tileIdxList.ToArray();
    }

    // =====================================================================
    // A* PATHFINDING – THREAD-SAFE (modifikované – CanMove namiesto IsPassable)
    // =====================================================================

    class AStarNode
    {
        public Vector2Int pos;
        public AStarNode parent;
        public float g, h;
        public float f => g + h;

        public AStarNode(Vector2Int p, AStarNode par, float g, float h)
        {
            pos = p; parent = par; this.g = g; this.h = h;
        }
    }

    static readonly Vector2Int[] DIRECTIONS =
    {
        new Vector2Int( 1, 0),  // Right
        new Vector2Int(-1, 0),  // Left
        new Vector2Int( 0, 1),  // Top
        new Vector2Int( 0,-1)   // Bottom
    };

    static List<Vector2Int> AStarPathThreaded(TileGridSnapshot snap, Vector2Int start, Vector2Int goal)
    {
        if (start == goal) return new List<Vector2Int>();

        var open = new List<AStarNode>();
        var closed = new HashSet<Vector2Int>();
        var nodeMap = new Dictionary<Vector2Int, AStarNode>();

        open.Add(new AStarNode(start, null, 0f, Heuristic(start, goal)));
        nodeMap[start] = open[0];

        int iterations = 0;
        const int MAX_ITER = 10000;

        while (open.Count > 0 && iterations < MAX_ITER)
        {
            iterations++;
            int bestIdx = 0;
            for (int i = 1; i < open.Count; i++)
                if (open[i].f < open[bestIdx].f) bestIdx = i;

            AStarNode current = open[bestIdx];
            open.RemoveAt(bestIdx);

            if (current.pos == goal)
                return ReconstructPath(current);

            closed.Add(current.pos);

            foreach (var dir in DIRECTIONS)
            {
                Vector2Int neighbor = current.pos + dir;
                if (closed.Contains(neighbor)) continue;

                IndicatrixAPI.DirectionMask dirMask = DirFromStep(dir);
                if (!CanMove(snap.tileIDs, snap.connections, current.pos, neighbor, dirMask))
                    continue;

                float newG = current.g + 1f;
                if (nodeMap.TryGetValue(neighbor, out AStarNode existing))
                {
                    if (newG < existing.g) { existing.g = newG; existing.parent = current; }
                }
                else
                {
                    var node = new AStarNode(neighbor, current, newG, Heuristic(neighbor, goal));
                    open.Add(node);
                    nodeMap[neighbor] = node;
                }
            }
        }

        return null;
    }

    static float Heuristic(Vector2Int a, Vector2Int b)
        => Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);

    static List<Vector2Int> ReconstructPath(AStarNode node)
    {
        var path = new List<Vector2Int>();
        var cur = node;
        while (cur.parent != null) { path.Add(cur.pos); cur = cur.parent; }
        path.Reverse();
        return path;
    }

    public void WriteSave(System.IO.BinaryWriter bw)
    {
        bw.Write(trains.Count);
        foreach (var kvp in trains)
        {
            TrainData td = kvp.Value;
            var consist = td.consist;

            bw.Write(td.depotX);
            bw.Write(td.depotZ);

            // Typy (indexy v katalógu – mapovanie cez Name, ktoré je unikátne).
            bw.Write(TrainTypeIndexOf(consist));
            bw.Write(WagonTypeIndexOf(consist));

            // Počet vagónov + vek.
            int wc = consist != null ? consist.Wagons.Count : 0;
            bw.Write(wc);
            bw.Write(consist != null ? consist.Age : 0);

            // Náklad jednotlivých vagónov.
            for (int i = 0; i < wc; i++)
                bw.Write(consist.Wagons[i].CurrentCapacity);

            // Priradené stanice.
            bw.Write(td.stations != null ? td.stations.Count : 0);
            if (td.stations != null)
                foreach (var s in td.stations) { bw.Write(s.x); bw.Write(s.y); }

            // Beh/stop.
            bw.Write(td.isRunning);
        }
    }

    public void ReadSave(System.IO.BinaryReader br)
    {
        int count = br.ReadInt32();
        for (int k = 0; k < count; k++)
        {
            int dx = br.ReadInt32();
            int dz = br.ReadInt32();
            int trainTypeIndex = br.ReadInt32();
            int wagonTypeIndex = br.ReadInt32();
            int wagonCnt = br.ReadInt32();
            int age = br.ReadInt32();

            int[] cargo = new int[wagonCnt];
            for (int i = 0; i < wagonCnt; i++) cargo[i] = br.ReadInt32();

            int stationCnt = br.ReadInt32();
            var stations = new System.Collections.Generic.List<Vector2Int>(stationCnt);
            for (int i = 0; i < stationCnt; i++)
                stations.Add(new Vector2Int(br.ReadInt32(), br.ReadInt32()));

            bool wasRunning = br.ReadBoolean();

            // CreateTrain použije pole `wagonCount` ako počet vagónov –
            // nastavíme ho na uloženú hodnotu (CreateTrain si ho ešte oreže 1..10).
            wagonCount = Mathf.Clamp(wagonCnt, 1, 10);
            if (!CreateTrain(dx, dz, trainTypeIndex, wagonTypeIndex))
                continue;

            TrainData td = GetTrain(dx, dz);
            if (td == null) continue;

            if (td.consist != null)
            {
                td.consist.Age = age;
                int n = Mathf.Min(cargo.Length, td.consist.Wagons.Count);
                for (int i = 0; i < n; i++)
                    td.consist.Wagons[i].CurrentCapacity = cargo[i];
            }

            foreach (var s in stations)
                AddStation(dx, dz, s.x, s.y);

            if (wasRunning)
                StartTrain(dx, dz);
        }
    }

    /// <summary>Index typu vlaku v TrainCatalog (podľa unikátneho Name); 0 ak sa nenájde.</summary>
    private int TrainTypeIndexOf(Game.TrainStock.TrainInstance consist)
    {
        if (consist == null) return 0;
        var all = Game.TrainStock.TrainCatalog.All;
        for (int i = 0; i < all.Count; i++)
            if (all[i].Name == consist.Spec.Name) return i;
        return 0;
    }

    /// <summary>Index typu vagónu v WagonCatalog (podľa Name 1. vagónu); 0 ak sa nenájde.</summary>
    private int WagonTypeIndexOf(Game.TrainStock.TrainInstance consist)
    {
        if (consist == null || consist.Wagons.Count == 0) return 0;
        string name = consist.Wagons[0].Spec.Name;
        var all = Game.TrainStock.WagonCatalog.All;
        for (int i = 0; i < all.Count; i++)
            if (all[i].Name == name) return i;
        return 0;
    }

}

TrainTradeSystem.cs

using System.Collections.Generic;
using UnityEngine;
using Game.TrainStock;

/// <summary>
/// TrainTradeSystem.cs
/// ------------------------------------------------------------------
/// TRANSAKČNÝ ALGORITMUS – výmena tovaru medzi vlakom a továrňami stanice.
///
/// Tento súbor implementuje to, čo zadanie nazýva "ALGORITMUS TRANSAKCIE
/// tovaru cez stanicu". Volá sa z TrainSystem-u v momente, keď vlak čaká
/// na stanici (po 2 s z 10 s čakania – časovanie rieši TrainSystem).
///
/// ------------------------------------------------------------------
/// AKO TO FUNGUJE – KONCEPT
///
/// Vlak má vagóny VŽDY len jedného typu (napr. "Coal Truck", #1). Typ
/// vagónu nesie ResourceType, s ktorým vie pracovať:
///     WagonType.Id == (int)ResourceType
///     ("Coal Truck" #1 → ResourceType.Coal,  "Wood Truck" #2 → Wood)
/// Vďaka tomu netreba do TrainStock.cs pridávať nové pole – mapovanie
/// je 1:1 cez číselné ID (pozri WagonResource() nižšie).
///
/// Stanica eviduje 0..N tovární vo svojej 9×9 zóne (StationInstance.Factories).
/// Každá továreň má pre danú surovinu buď VÝDAJOVÝ slot (UnLoad – surovinu
/// produkuje, napr. Coal Mine) alebo PRÍJMOVÝ slot (Load – surovinu
/// spotrebúva, napr. Power Station).
///
/// Keď vlak zastane na stanici, pre svoj typ suroviny urobí:
///
///   KROK 1 – VYLOŽENIE (Unload):
///     Ak je v zóne stanice továreň, ktorá danú surovinu PRIJÍMA (Load slot),
///     a vo vagónoch niečo je → vyprázdni vagóny do tej továrne (Load slot
///     amount sa zvýši, vagóny idú na 0).
///
///   KROK 2 – NALOŽENIE (Load):
///     Ak je v zóne stanice továreň, ktorá danú surovinu VYDÁVA (UnLoad slot),
///     a vo vagónoch je voľné miesto → naloží z tej továrne do vagónov
///     (UnLoad slot amount sa zníži, vagóny sa naplnia "alikvotne":
///     vagón po vagóne do max kapacity – pozri FillWagons()).
///
/// Obchod sa pokladá za ÚSPEŠNÝ ("predaj prebehol") IBA vtedy, keď sa pri
/// zastávke SKUTOČNE VYLOŽILA aspoň 1 jednotka tovaru do príjmovej továrne.
/// Samotné naloženie surovín do vagónov NEROBÍ obchod úspešným a negeneruje
/// žiadny výnos – je to len presun, príprava na predaj na ďalšej stanici.
///
/// BONUS zo zadania (vlak príde viackrát do výdajovej stanice a vo vagónoch
/// už niečo má): rieši sa automaticky. KROK 1 sa vykoná len ak je v zóne
/// PRÍJMOVÁ továreň – pri čisto výdajovej stanici sa teda nevyloží a KROK 2
/// len pripočíta ďalšiu surovinu k tomu, čo už vo vagónoch je (FillWagons
/// dopĺňa do voľného miesta, existujúci náklad nemaže).
///
/// ------------------------------------------------------------------
/// CENA / VÝNOS
///
/// Po úspešnom obchode (vyloží sa aspoň 1 jednotka do príjmovej továrne) sa
/// vyčísli zárobok cez centrálny cenník <see cref="ResourcePricing"/>:
/// cena je definovaná ZA PLNÝ VAGÓN podľa suroviny (napr. Coal 50, Oil 70,
/// Furniture 75) a ak sa predal len zlomok plného vagónu, počíta sa ALIKVOTNE.
/// Cena sa navyše násobí dynamickým násobiteľom konta (pri zápornom konte
/// rastie – pozri ResourcePricing / EconomySystem). Výslednú sumu pripíše na
/// konto volajúci (TrainSystem) a vypíše log so sumou v CR.
/// ------------------------------------------------------------------
/// </summary>
public static class TrainTradeSystem
{
    // Cena predaja sa už NEráta paušálne za jednotku. Kompletný cenník (cena
    // ZA PLNÝ VAGÓN podľa suroviny + alikvotná časť pri neúplnom vagóne +
    // dynamický násobiteľ pri zápornom konte) je centralizovaný v
    // ResourcePricing.RailSaleRevenue(...). Pozri krok 4 v Execute(...).

    // ==================================================================
    // VÝSLEDOK TRANSAKCIE
    // ==================================================================

    /// <summary>
    /// Výsledok jednej zastávky vlaku na stanici. TrainSystem si z neho
    /// prečíta Success + Revenue a vypíše príslušný Debug.Log.
    /// </summary>
    public struct TradeResult
    {
        /// <summary>True, ak sa presunula aspoň 1 jednotka tovaru.</summary>
        public bool Success;

        /// <summary>Koľko jednotiek sa naložilo z továrne do vagónov.</summary>
        public int LoadedUnits;

        /// <summary>Koľko jednotiek sa vyložilo z vagónov do továrne.</summary>
        public int UnloadedUnits;

        /// <summary>Celkový výnos z obchodu (euro).</summary>
        public int Revenue;

        /// <summary>Stručný textový dôvod (najmä pri neúspechu) – na debug.</summary>
        public string Reason;

        /// <summary>Celkový počet presunutých jednotiek (load + unload).</summary>
        public int MovedUnits => LoadedUnits + UnloadedUnits;
    }

    // ==================================================================
    // HLAVNÝ VSTUP – vykonaj transakciu pre vlak na stanici
    // ==================================================================

    /// <summary>
    /// Vykoná výmenu tovaru medzi súpravou <paramref name="consist"/> a
    /// továrňami v zóne stanice <paramref name="station"/>.
    ///
    /// Postup:
    ///   0) Validácia (consist má vagóny, stanica eviduje aspoň 1 továreň).
    ///   1) Zisti surovinu, s ktorou vlak pracuje (z typu vagónov).
    ///   2) UNLOAD – ak je v zóne príjmová továreň, vyprázdni vagóny do nej.
    ///   3) LOAD   – ak je v zóne výdajová továreň, naplň vagóny z nej.
    ///   4) Vyhodnoť úspech a vyčísli cenu.
    ///
    /// Metóda je čisto dátová (žiadne Unity GameObject API), takže sa dá
    /// volať z hlavného vlákna v Update slučke TrainSystem-u bez rizika.
    /// </summary>
    public static TradeResult Execute(TrainInstance consist, StationInstance station)
    {
        var result = new TradeResult
        {
            Success = false,
            LoadedUnits = 0,
            UnloadedUnits = 0,
            Revenue = 0,
            Reason = ""
        };

        // ---- 0) VALIDÁCIA -------------------------------------------------
        if (consist == null)
        {
            result.Reason = "vlak nemá dátovú súpravu (consist == null)";
            return result;
        }
        if (consist.Wagons == null || consist.Wagons.Count == 0)
        {
            result.Reason = "vlak nemá žiadne vagóny";
            return result;
        }
        if (station == null)
        {
            result.Reason = "neznáma stanica";
            return result;
        }
        if (!station.HasFactories)
        {
            result.Reason = "stanica neeviduje žiadnu továreň v 9×9 zóne";
            return result;
        }

        // ---- 1) SUROVINA VLAKU -------------------------------------------
        // Všetky vagóny sú rovnakého typu – stačí pozrieť prvý.
        ResourceType wagonResource = WagonResource(consist.Wagons[0]);
        if (wagonResource == ResourceType.None)
        {
            result.Reason = $"typ vagónu '{consist.Wagons[0].Type}' nemá priradenú surovinu";
            return result;
        }

        // ---- 2) UNLOAD (vyloženie do príjmovej továrne) -------------------
        // Vykoná sa len ak je v zóne stanice továreň, ktorá danú surovinu
        // PRIJÍMA. Pri čisto výdajovej stanici sa tento krok preskočí –
        // tým je ošetrený "bonus": vlak môže prejsť cez výdajovú stanicu
        // a náklad mu zostane vo vagónoch.
        FactoryInstance consumer = station.FindConsumer(wagonResource);
        if (consumer != null)
        {
            ResourceSlot loadSlot = consumer.GetLoadSlot(wagonResource);
            result.UnloadedUnits = UnloadWagons(consist, loadSlot);
            if (result.UnloadedUnits > 0)
                Debug.Log($"[TradeSystem] Vyložené {result.UnloadedUnits}× {wagonResource} " +
                          $"do '{consumer.Name}' (slot {loadSlot}).");
        }

        // ---- 3) LOAD (naloženie z výdajovej továrne) ----------------------
        // Vykoná sa len ak je v zóne stanice továreň, ktorá danú surovinu
        // VYDÁVA. FillWagons dopĺňa do VOĽNÉHO miesta, takže existujúci
        // náklad vo vagónoch (z inej stanice) zostáva a nová surovina sa
        // k nemu pripočíta.
        FactoryInstance supplier = station.FindSupplier(wagonResource);
        if (supplier != null)
        {
            ResourceSlot unloadSlot = supplier.GetUnLoadSlot(wagonResource);
            result.LoadedUnits = FillWagons(consist, unloadSlot);
            if (result.LoadedUnits > 0)
                Debug.Log($"[TradeSystem] Naložené {result.LoadedUnits}× {wagonResource} " +
                          $"z '{supplier.Name}' (slot {unloadSlot}).");
        }

        // ---- 4) VYHODNOTENIE ---------------------------------------------
        // DÔLEŽITÉ: peniaze plynú IBA z VYLOŽENIA (predaja tovaru továrni).
        // Naloženie surovín z výdajovej továrne do vagónov je len presun –
        // NEGENERUJE žiadny výnos a samo o sebe nerobí obchod úspešným.
        //
        // Obchod sa teda pokladá za úspešný ("Predaj prebehol úspešne")
        // práve vtedy, keď sa do nejakej príjmovej továrne SKUTOČNE vyložila
        // aspoň 1 jednotka tovaru. Cena = počet VYLOŽENÝCH jednotiek.
        result.Success = result.UnloadedUnits > 0;

        if (result.Success)
        {
            // Cena ZA PLNÝ VAGÓN podľa suroviny, alikvotne podľa reálne
            // predaného (vyloženého) množstva, prenásobená aktuálnym cenovým
            // násobiteľom konta. Všetky vagóny sú rovnakého typu → rovnaká
            // MaximumCapacity, ktorá je menovateľom alikvotného prepočtu.
            int wagonCapacity = FirstWagonCapacity(consist);
            result.Revenue = ResourcePricing.RailSaleRevenue(
                wagonResource, result.UnloadedUnits, wagonCapacity);
        }
        else
        {
            // Žiadny PREDAJ (vyloženie) – upresni dôvod pre debug.
            // Pozn.: aj keď sa niečo naložilo (LoadedUnits > 0), obchod sa
            // bez vyloženia neráta ako úspešný – naloženie je len príprava
            // na predaj na nasledujúcej stanici.
            if (consumer == null)
            {
                if (result.LoadedUnits > 0)
                    result.Reason = $"naložené {result.LoadedUnits}× {wagonResource}, " +
                                    $"ale stanica nemá príjmovú továreň – predaj sa nekoná";
                else if (supplier == null)
                    result.Reason = $"v zóne stanice nie je továreň pre surovinu {wagonResource}";
                else
                    result.Reason = "stanica nemá príjmovú továreň – nie je kam predať";
            }
            else
            {
                result.Reason = "vagóny prázdne alebo príjmová továreň plná – nič sa nevyložilo";
            }
        }

        return result;
    }

    // ==================================================================
    // POMOCNÉ – kapacita jedného vagónu (menovateľ alikvotnej ceny)
    // ==================================================================

    /// <summary>
    /// Maximálna kapacita JEDNÉHO vagónu súpravy. Všetky vagóny vlaku sú
    /// rovnakého typu (rovnaká MaximumCapacity), takže stačí prvý platný.
    /// Slúži ako menovateľ pri výpočte ceny "za plný vagón" (alikvotne).
    /// Vráti 0, ak súprava nemá platný vagón (vtedy je zárobok 0 CR).
    /// </summary>
    private static int FirstWagonCapacity(TrainInstance consist)
    {
        if (consist == null || consist.Wagons == null) return 0;
        for (int i = 0; i < consist.Wagons.Count; i++)
        {
            var w = consist.Wagons[i];
            if (w != null && w.MaximumCapacity > 0) return w.MaximumCapacity;
        }
        return 0;
    }

    // ==================================================================
    // POMOCNÉ – mapovanie typu vagónu na surovinu
    // ==================================================================

    /// <summary>
    /// Vráti surovinu, s ktorou vie daný vagón pracovať.
    ///
    /// Mapovanie je cez ČÍSELNÉ ID: WagonType.Id == (int)ResourceType.
    /// Katalógy sú zámerne zladené:
    ///     "Coal Truck" má Id 1  →  ResourceType.Coal == 1
    ///     "Wood Truck" má Id 2  →  ResourceType.Wood == 2
    /// Vďaka tomu sa do TrainStock.cs nemusí pridávať nové pole.
    ///
    /// Ak by v budúcnosti ID nesedeli, stačí toto mapovanie nahradiť
    /// explicitnou tabuľkou (Dictionary&lt;int, ResourceType&gt;).
    /// </summary>
    public static ResourceType WagonResource(WagonInstance wagon)
    {
        if (wagon == null) return ResourceType.None;

        int id = wagon.Type.Id;
        if (System.Enum.IsDefined(typeof(ResourceType), id))
            return (ResourceType)id;

        return ResourceType.None;
    }

    // ==================================================================
    // POMOCNÉ – LOAD: naplnenie vagónov z výdajového slotu továrne
    // ==================================================================

    /// <summary>
    /// "Alikvotné" naplnenie vagónov surovinou zo skladu továrne
    /// (<paramref name="source"/> = UnLoad slot).
    ///
    /// ALGORITMUS (presne podľa príkladu zo zadania):
    ///   Ide vagón po vagóne. Každý vagón sa naplní do svojej maximálnej
    ///   kapacity, pokým je v továrni surovina k dispozícii. Posledný
    ///   čiastočne naplnený vagón dostane zvyšok.
    ///
    ///   Príklad zo zadania: 8 vagónov × max 30 = kapacita 240, v továrni
    ///   je 173 → naplní sa 5 plných vagónov (5×30 = 150) + 6. vagón
    ///   dostane zvyšok 23. Vagóny 7 a 8 zostanú prázdne. Z továrne sa
    ///   odpočíta presne 173 (resp. 150+23 = 173).
    ///
    /// Rešpektuje aj BONUS: ak vo vagóne už nejaká surovina je (z inej
    /// stanice), dopĺňa sa len jeho VOĽNÉ miesto – existujúce množstvo
    /// sa nemaže.
    ///
    /// Vráti, koľko jednotiek sa SKUTOČNE naložilo (a teda aj odpočítalo
    /// z továrne).
    /// </summary>
    public static int FillWagons(TrainInstance consist, ResourceSlot source)
    {
        if (consist == null || source == null) return 0;
        if (source.IsEmpty) return 0;

        int totalLoaded = 0;

        foreach (var wagon in consist.Wagons)
        {
            if (wagon == null) continue;

            // Voľné miesto v tomto vagóne.
            int free = wagon.MaximumCapacity - wagon.CurrentCapacity;
            if (free <= 0) continue;            // vagón je plný – ďalší

            // Koľko ešte zostáva v továrni.
            int available = source.amount - totalLoaded;
            if (available <= 0) break;          // továreň vyčerpaná – koniec

            // Do vagónu ide min(voľné miesto, dostupné v továrni).
            int toLoad = Mathf.Min(free, available);
            wagon.CurrentCapacity += toLoad;
            totalLoaded += toLoad;
        }

        // Odpočítaj naloženú surovinu zo skladu továrne.
        // ResourceSlot.Remove je orezané dostupným množstvom (bezpečné).
        if (totalLoaded > 0)
            source.Remove(totalLoaded);

        return totalLoaded;
    }

    // ==================================================================
    // POMOCNÉ – UNLOAD: vyprázdnenie vagónov do príjmového slotu továrne
    // ==================================================================

    /// <summary>
    /// Vyprázdnenie vagónov do skladu továrne (<paramref name="target"/>
    /// = Load slot).
    ///
    /// ALGORITMUS:
    ///   Ide vagón po vagóne. Z každého vagónu sa presunie jeho náklad do
    ///   príjmového slotu továrne. Slot má kapacitu – ak by sa nezmestilo
    ///   všetko, presunie sa len toľko, koľko je v slote voľné, a zvyšok
    ///   ZOSTANE vo vagóne (vlak ho odvezie ďalej).
    ///
    ///   V základnom scenári zadania ("všetky vagóny vyprázdni") má
    ///   príjmová továreň dosť kapacity (Power Station Load 0/300), takže
    ///   sa vagóny vyprázdnia úplne. Orezanie kapacitou je poistka pre
    ///   prípad plnej továrne.
    ///
    /// Vráti, koľko jednotiek sa SKUTOČNE vyložilo (a pripočítalo k továrni).
    /// </summary>
    public static int UnloadWagons(TrainInstance consist, ResourceSlot target)
    {
        if (consist == null || target == null) return 0;

        int totalUnloaded = 0;

        foreach (var wagon in consist.Wagons)
        {
            if (wagon == null) continue;
            if (wagon.CurrentCapacity <= 0) continue;   // prázdny vagón

            // Voľné miesto v príjmovom slote továrne (po doterajšom vykladaní).
            int free = target.capacity - (target.amount + totalUnloaded);
            if (free <= 0) break;                       // továreň plná – koniec

            // Z vagónu presunieme min(jeho náklad, voľné miesto v továrni).
            int toUnload = Mathf.Min(wagon.CurrentCapacity, free);
            wagon.CurrentCapacity -= toUnload;
            totalUnloaded += toUnload;
        }

        // Pripočítaj vyloženú surovinu do skladu továrne.
        // ResourceSlot.Add je orezané kapacitou (bezpečné).
        if (totalUnloaded > 0)
            target.Add(totalUnloaded);

        return totalUnloaded;
    }
}

VehicleStock.cs

using System;
using System.Collections.Generic;

/// <summary>
/// VehicleStock.cs
/// ─────────────────────────────────────────────────────────────────────────
/// VOZIDLOVÝ PARK – čisté dátové štruktúry (žiadne MonoBehaviour, žiadne
/// Unity API).
///
/// Tento súbor je zámerne oddelený od VehicleSystem.cs, presne tak ako je
/// TrainStock.cs oddelený od TrainSystem.cs:
///   • VehicleSystem.cs = pohybová logika, A* pathfinding, vizuál (kváder).
///   • VehicleStock.cs  = "čo je vozidlo" – iba dáta a katalógy.
///
/// ─────────────────────────────────────────────────────────────────────────
/// VLAK vs. VOZIDLO – intuitívne porovnanie
///
///   Vlak  = lokomotíva (TrainSpec/TrainInstance) + zoznam vagónov
///           (WagonSpec/WagonInstance). Náklad prevážajú vagóny.
///
///   Vozidlo = JEDEN kváder. Nemá vagóny. Ale samotný kváder má vlastnosť
///             "akoby jedného vagónu" – môže prevážať náklad v nejakom
///             množstve. Preto VehicleSpec spája do jednej karty
///             vlakové parametre (Cost, Speed, Power...) aj vagónové
///             parametre (MaximumCapacity, typ nákladu...).
///
/// ─────────────────────────────────────────────────────────────────────────
/// ROZDELENIE: "Spec" (predloha) vs. "Instance" (inštancia v hre)
///
///   *Spec     – nemenná predloha typu (katalógová karta). Spoločná pre
///               všetky vozidlá toho istého typu. Napr. VehicleSpec
///               "Coal Truck" má Cost, Speed, Power... – tieto hodnoty sú
///               rovnaké pre každý vyrobený kus.
///
///   *Instance – konkrétny kus v hre, vytvorený v depe. Drží referenciu na
///               svoj Spec + premenlivý stav daného kusu (Age,
///               CurrentCapacity – tie sa líšia kus od kusu a menia sa
///               počas hry).
///
/// Vďaka tomu sa nemenné parametre (Cost, Speed...) NEDUPLIKUJÚ do každého
/// vozidla – sú raz v katalógu. UI okno s detailmi vozidla si ich len
/// prečíta cez Instance.Spec.
/// ─────────────────────────────────────────────────────────────────────────
///
/// POZNÁMKA K TYPOM (revízia):
///   Polia Weight a Power (VehicleSpec) sú modelované ako int (číselné
///   hodnoty), nie ako string s jednotkou. Pôvodne to boli stringy
///   ("2t", "300hp"), ale nikde mimo tohto súboru sa nečítali a zadané dáta
///   vozidiel sú číselné. Číselný typ je vhodnejší (dá sa s ním počítať).
///   YearOfManufacture zostáva string ("1984") – rok sa nikde nepočíta a v
///   zadaní je uvedený ako textová hodnota.
/// ─────────────────────────────────────────────────────────────────────────
/// </summary>

namespace Game.VehicleStock
{
    // =========================================================================
    // TYP VOZIDLA
    //
    // Zadanie hovorí o "hash table (string, int)" = [typ vozidla, číselné
    // označenie vozidla]. To je ale len JEDNA dvojica hodnôt pre jeden typ
    // vozidla – nie kolekcia. Použiť Dictionary/Hashtable na uloženie jednej
    // dvojice je zbytočné (alokácia, žiadna typová bezpečnosť, neprehľadné).
    //
    // Preto je typ vozidla modelovaný ako malý nemenný struct VehicleType
    // (Name + Id) – presne ako WagonType v TrainStock.cs. "Hash table"
    // charakter – teda vyhľadanie typu podľa názvu alebo podľa čísla –
    // zabezpečuje statický register VehicleCatalog, ktorý skutočné slovníky
    // (ByName, ById) drží centrálne na jednom mieste.
    //
    // Ak by si predsa len chcel priamo slovník na konkrétnom vozidle, dá sa
    // získať jednoriadkovo: VehicleType.AsPair() vráti KeyValuePair<string,int>.
    //
    // DÔLEŽITÉ – ZHODA ID S ResourceType (FactorySystem.cs):
    //   Číselné Id typu vozidla je ZÁMERNE rovnaké ako (int)ResourceType:
    //     "Coal Truck"        #1  → ResourceType.Coal                = 1
    //     "Wood Truck"        #2  → ResourceType.Wood                = 2
    //     "Iron Ore Truck"    #3  → ResourceType.IronOre             = 3
    //     ...
    //     "Electronics Truck" #16 → ResourceType.ElectronicsProducts = 16
    //   Vďaka tomu VehicleTradeSystem.VehicleResource() namapuje vozidlo na
    //   surovinu bez extra poľa (Id == (int)ResourceType). Pri pridávaní
    //   nového vozidla teda Id MUSÍ zodpovedať príslušnej hodnote v enum
    //   ResourceType.
    // =========================================================================

    /// <summary>
    /// Typ vozidla – nemenná dvojica [textové označenie, číselné označenie].
    /// Napr. ("Coal Truck", 1), ("Wood Truck", 2).
    /// </summary>
    [Serializable]
    public readonly struct VehicleType : IEquatable<VehicleType>
    {
        /// <summary>Textové označenie typu vozidla, napr. "Coal Truck".</summary>
        public readonly string Name;

        /// <summary>Číselné označenie typu vozidla (== (int)ResourceType), napr. 1.</summary>
        public readonly int Id;

        public VehicleType(string name, int id)
        {
            Name = name;
            Id = id;
        }

        /// <summary>
        /// Vráti typ vozidla ako dvojicu kľúč–hodnota (string, int).
        /// Pohodlné, ak niektorá časť kódu očakáva "hash table" reprezentáciu.
        /// </summary>
        public KeyValuePair<string, int> AsPair() => new KeyValuePair<string, int>(Name, Id);

        public bool Equals(VehicleType other) => Id == other.Id && Name == other.Name;
        public override bool Equals(object obj) => obj is VehicleType v && Equals(v);
        public override int GetHashCode() => Id;
        public override string ToString() => $"{Name} (#{Id})";
    }

    // =========================================================================
    // PREDLOHA VOZIDLA – VehicleSpec
    //
    // Nemenná katalógová karta jedného typu vozidla. Obsahuje VŠETKY
    // nemenné atribúty zo zadania:
    //   Name, Type, MaximumCapacity, Cost, OperatingCosts, Speed, Weight,
    //   Power, ServiceLife, ServicingInterval, YearOfManufacture.
    //
    // Premenlivé atribúty (CurrentCapacity, Age) NEPATRIA sem – tie sú na
    // VehicleInstance, lebo sa líšia kus od kusu a menia sa počas hry.
    // =========================================================================

    /// <summary>
    /// Predloha (katalógová karta) jedného typu vozidla.
    /// Nemenné parametre spoločné pre všetky kusy daného typu.
    /// </summary>
    [Serializable]
    public sealed class VehicleSpec
    {
        /// <summary>Názov vozidla, napr. "Coal Truck".</summary>
        public string Name;

        /// <summary>Typ vozidla – [textové označenie, číselné označenie].</summary>
        public VehicleType Type;

        /// <summary>Maximálna kapacita vozidla.</summary>
        public int MaximumCapacity;

        /// <summary>Cena vozidla.</summary>
        public int Cost;

        /// <summary>Prevádzkové náklady.</summary>
        public int OperatingCosts;

        /// <summary>Rýchlosť vozidla.</summary>
        public int Speed;

        /// <summary>Hmotnosť vozidla (číselne, napr. 2).</summary>
        public int Weight;

        /// <summary>Výkon vozidla (číselne, napr. 100).</summary>
        public int Power;

        /// <summary>Životnosť vozidla.</summary>
        public int ServiceLife;

        /// <summary>Servisný interval.</summary>
        public int ServicingInterval;

        /// <summary>Rok výroby, napr. "1984".</summary>
        public string YearOfManufacture;

        public VehicleSpec(string name, VehicleType type, int maximumCapacity,
                           int cost, int operatingCosts, int speed, int weight,
                           int power, int serviceLife, int servicingInterval,
                           string yearOfManufacture)
        {
            Name = name;
            Type = type;
            MaximumCapacity = maximumCapacity;
            Cost = cost;
            OperatingCosts = operatingCosts;
            Speed = speed;
            Weight = weight;
            Power = power;
            ServiceLife = serviceLife;
            ServicingInterval = servicingInterval;
            YearOfManufacture = yearOfManufacture;
        }
    }

    // =========================================================================
    // INŠTANCIA VOZIDLA – VehicleInstance
    //
    // Konkrétny kus vozidla existujúci v hre, vytvorený v depe. Drží
    // referenciu na svoj VehicleSpec (nemenné parametre) + premenlivý stav
    // daného kusu:
    //   • CurrentCapacity – aktuálne naložený náklad (pri vytvorení 0).
    //   • Age             – aktuálny vek vozidla (pri vytvorení 0).
    //
    // Pozn.: VehicleInstance je čisto dátový popis. Pohybový/pathfinding
    // stav (activePath, vehicleDistance, isRunning, stanice...) zostáva v
    // VehicleSystem.VehicleData. VehicleData drží referenciu na
    // VehicleInstance – pozri integráciu vo VehicleSystem.cs.
    // =========================================================================

    /// <summary>
    /// Konkrétne vozidlo existujúce v hre. Drží referenciu na svoj
    /// VehicleSpec (nemenné parametre) + premenlivý stav (Age, CurrentCapacity).
    /// </summary>
    [Serializable]
    public sealed class VehicleInstance
    {
        /// <summary>Predloha typu tohto vozidla (nemenné parametre).</summary>
        public readonly VehicleSpec Spec;

        /// <summary>
        /// Aktuálna kapacita (naložený náklad). Pri vytvorení 0.
        /// Mení sa počas hry pri nakladaní/vykladaní.
        /// </summary>
        public int CurrentCapacity;

        /// <summary>
        /// Aktuálny vek vozidla. Pri vytvorení 0.
        /// Mení sa počas hry (pribúdaním herného času).
        /// </summary>
        public int Age;

        public VehicleInstance(VehicleSpec spec)
        {
            Spec = spec ?? throw new ArgumentNullException(nameof(spec));
            CurrentCapacity = 0;
            Age = 0;
        }

        // ----- Pohodlné skratky na nemenné parametre (čítané z UI okna) -----
        public string Name => Spec.Name;
        public VehicleType Type => Spec.Type;
        public int MaximumCapacity => Spec.MaximumCapacity;
        public int Cost => Spec.Cost;
        public int OperatingCosts => Spec.OperatingCosts;
        public int Speed => Spec.Speed;
        public int Weight => Spec.Weight;
        public int Power => Spec.Power;
        public int ServiceLife => Spec.ServiceLife;
        public int ServicingInterval => Spec.ServicingInterval;
        public string YearOfManufacture => Spec.YearOfManufacture;
    }

    // =========================================================================
    // KATALÓG VOZIDIEL – VehicleCatalog
    //
    // Centrálne miesto, kde sú definované všetky typy vozidiel. Slúži ako
    // zdroj pre VehicleTypeDropdown (názvy) aj pre VehicleSystem (vytvorenie
    // inštancie). Drží aj "hash table" lookupy (ByName, ById) – to je
    // miesto, kde má slovník skutočný zmysel (kolekcia viacerých typov).
    //
    // Id každého vozidla sa zhoduje s (int)ResourceType (FactorySystem.cs),
    // takže keď vozidlo dorazí do stanice, VehicleTradeSystem.VehicleResource()
    // automaticky rozpozná surovinu pre ktorýkoľvek z týchto 16 typov.
    // =========================================================================

    public static class VehicleCatalog
    {
        /// <summary>
        /// Všetky dostupné typy vozidiel, v poradí zhodnom s VehicleTypeDropdown.
        /// 16 typov – Id zodpovedá ResourceType (1 = Coal ... 16 = Electronics).
        ///
        /// Pozn.: Name aj Type.Name sú teraz zhodné (napr. "Coal Truck"),
        /// takže dropdown zobrazuje priamo názvy nákladných vozidiel a
        /// VehicleCatalog.ByName(...) ich vie rozlíšiť (názvy sú unikátne).
        /// </summary>
        public static readonly IReadOnlyList<VehicleSpec> All = new List<VehicleSpec>
        {
            // Vozidlo 1 – upravené
            new VehicleSpec(
                name:              "Coal Truck",
                type:              new VehicleType("Coal Truck", 1),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1984"
            ),
            // Vozidlo 2 – upravené
            new VehicleSpec(
                name:              "Wood Truck",
                type:              new VehicleType("Wood Truck", 2),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1984"
            ),
            // Vozidlo 3
            new VehicleSpec(
                name:              "Iron Ore Truck",
                type:              new VehicleType("Iron Ore Truck", 3),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1997"
            ),
            // Vozidlo 4
            new VehicleSpec(
                name:              "Gold Truck",
                type:              new VehicleType("Gold Truck", 4),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1995"
            ),
            // Vozidlo 5
            new VehicleSpec(
                name:              "Silver Truck",
                type:              new VehicleType("Silver Truck", 5),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1992"
            ),
            // Vozidlo 6
            new VehicleSpec(
                name:              "Livestock Truck",
                type:              new VehicleType("Livestock Truck", 6),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1989"
            ),
            // Vozidlo 7
            new VehicleSpec(
                name:              "Grain Truck",
                type:              new VehicleType("Grain Truck", 7),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1988"
            ),
            // Vozidlo 8
            new VehicleSpec(
                name:              "Oil Truck",
                type:              new VehicleType("Oil Truck", 8),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1977"
            ),
            // Vozidlo 9
            new VehicleSpec(
                name:              "Boards Truck",
                type:              new VehicleType("Boards Truck", 9),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1977"
            ),
            // Vozidlo 10
            new VehicleSpec(
                name:              "Plastic Truck",
                type:              new VehicleType("Plastic Truck", 10),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1973"
            ),
            // Vozidlo 11
            new VehicleSpec(
                name:              "Meat Truck",
                type:              new VehicleType("Meat Truck", 11),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1969"
            ),
            // Vozidlo 12
            new VehicleSpec(
                name:              "Flour Truck",
                type:              new VehicleType("Flour Truck", 12),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1967"
            ),
            // Vozidlo 13
            new VehicleSpec(
                name:              "Metals Truck",
                type:              new VehicleType("Metals Truck", 13),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1981"
            ),
            // Vozidlo 14
            new VehicleSpec(
                name:              "Glass Truck",
                type:              new VehicleType("Glass Truck", 14),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1981"
            ),
            // Vozidlo 15
            new VehicleSpec(
                name:              "Furniture Truck",
                type:              new VehicleType("Furniture Truck", 15),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1982"
            ),
            // Vozidlo 16
            new VehicleSpec(
                name:              "Electronics Truck",
                type:              new VehicleType("Electronics Truck", 16),
                maximumCapacity:   5,
                cost:              500,
                operatingCosts:    75,
                speed:             60,
                weight:            2,
                power:             100,
                serviceLife:       10,
                servicingInterval: 500,
                yearOfManufacture: "1975"
            ),
        };

        // "Hash table" lookupy – tu má slovník zmysel, lebo ide o kolekciu typov.
        // _byName a _byTypeName sú dva pohľady:
        //   _byName     – kľúč = názov vozidla    ("Coal Truck", "Wood Truck", ...)
        //   _byTypeName – kľúč = textové označenie typu ("Coal Truck", "Wood Truck", ...)
        // (Po revízii sú Name a Type.Name zhodné, takže oba slovníky majú
        //  rovnaké kľúče; ponechané sú obidva kvôli spätnej kompatibilite API.)
        private static readonly Dictionary<string, VehicleSpec> _byName = BuildByName();
        private static readonly Dictionary<string, VehicleSpec> _byTypeName = BuildByTypeName();
        private static readonly Dictionary<int, VehicleSpec> _byTypeId = BuildByTypeId();

        private static Dictionary<string, VehicleSpec> BuildByName()
        {
            var d = new Dictionary<string, VehicleSpec>();
            foreach (var v in All) d[v.Name] = v;
            return d;
        }

        private static Dictionary<string, VehicleSpec> BuildByTypeName()
        {
            var d = new Dictionary<string, VehicleSpec>();
            foreach (var v in All) d[v.Type.Name] = v;
            return d;
        }

        private static Dictionary<int, VehicleSpec> BuildByTypeId()
        {
            var d = new Dictionary<int, VehicleSpec>();
            foreach (var v in All) d[v.Type.Id] = v;
            return d;
        }

        /// <summary>Názvy typov vozidiel – priamo použiteľné pre VehicleTypeDropdown.</summary>
        public static string[] Names()
        {
            var names = new string[All.Count];
            for (int i = 0; i < All.Count; i++) names[i] = All[i].Name;
            return names;
        }

        /// <summary>Vráti VehicleSpec podľa indexu v dropdowne (bezpečné voči rozsahu).</summary>
        public static VehicleSpec ByIndex(int index)
        {
            if (index < 0 || index >= All.Count) return null;
            return All[index];
        }

        /// <summary>Vráti VehicleSpec podľa názvu vozidla ("Coal Truck"), alebo null.</summary>
        public static VehicleSpec ByName(string name)
            => name != null && _byName.TryGetValue(name, out var v) ? v : null;

        /// <summary>Vráti VehicleSpec podľa textového označenia typu ("Coal Truck"), alebo null.</summary>
        public static VehicleSpec ByTypeName(string typeName)
            => typeName != null && _byTypeName.TryGetValue(typeName, out var v) ? v : null;

        /// <summary>Vráti VehicleSpec podľa číselného označenia typu (1, 2, ...), alebo null.</summary>
        public static VehicleSpec ByTypeId(int typeId)
            => _byTypeId.TryGetValue(typeId, out var v) ? v : null;
    }
}

VehicleSystem.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using Game.VehicleStock;

/// <summary>
/// VehicleSystem.cs
///
/// CESTNÝ EKVIVALENT TrainSystem.cs – plne analogický systém pre ROAD
/// dopravu. Funguje úplne autonómne a nezávisle od TrainSystem.
///
/// PARALELIZMUS a DEBOUNCE:
/// ─────────────────────────────────────────────────────────────────────────
/// ✔ A* pathfinding beží na Thread Pool cez Task.Run (AStarPathThreaded).
///   Pracuje s int[] snapshotom road grafu – žiadne Unity API z vlákna.
///   Výsledky sa prenášajú cez ConcurrentQueue, aplikujú sa v Update().
///
/// ✔ OnMapChanged() – DEBOUNCE:
///   Každé volanie len nastaví flag + resetuje timer.
///   Update() odpočítava timer; až po uplynutí DEBOUNCE_DELAY od posledného
///   kliku spustí RoadSnapshot + Task.Run A* pre bežiace vozidlá.
///
/// ✘ UpdateVehicle / pohyb – hlavné vlákno (Unity API, thread-unsafe).
/// ✘ CreateVehicle / RemoveVehicle – hlavné vlákno (GameObject API).
/// ─────────────────────────────────────────────────────────────────────────
///
/// CESTNÉ VOZIDLO – "Distance-Based Path System":
/// ─────────────────────────────────────────────────────────────────────────
/// Najväčší rozdiel oproti TrainSystem: vozidlo je JEDINÝ kváder
/// (žiadne vagóny). Žiadny wagonCount, žiadny wagonSpacing.
///
/// • Trasa = PathData (List<Vector3> bodov + predpočítané kumulatívne dĺžky).
///   Zdroj: A* ako List<Vector2Int> → konvertovaný na waypointy cez
///   BuildWaypointPath (centrá + hrany + krivkové rohy).
/// • Vozidlo má skalárnu hodnotu vehicleDistance (vzdialenosť pozdĺž cesty).
///   Každý frame: vehicleDistance += speed * deltaTime.
/// • Pozícia = PathData.GetPositionAtDistance(d)   → Vector3.Lerp na segmente.
/// • Rotácia = PathData.GetDirectionAtDistance(d)  → Quaternion.LookRotation.
/// • Deterministické: rovnaký vstup → rovnaký výstup každý frame.
///
/// WAYPOINT-BASED MOVEMENT (zhodné s TrainSystem):
///   Trasa nie je center→center. Pre každý prechod A→B sa generuje:
///     A.center → A.edge(smer A→B) → B.edge(smer B→A) → B.center
///   Krivka:    B.entryEdge → B.exitEdge  (priama diagonála ~45°)
///   T-križovatka (RoadSwitch):
///     • PRIAMY: B.entryEdge → B.center → B.exitEdge
///     • ODBOČKA: B.entryEdge → B.innerEntry → B.innerExit → B.exitEdge
///   Križovatka (RoadCrossroad):
///     • PRIAMY: B.entryEdge → B.center → B.exitEdge (90°)
///     • ROHOVÝ: B.entryEdge → B.innerEntry → B.innerExit → B.exitEdge (45°)
///   Krivka, odbočka aj rohový prechod používajú VÝHRADNE priame čiarové
///   segmenty spojené lineárnym Lerp-om.
///
/// PREJAZD CEZ SVAH (LevelUp / LevelDown):
///   Identická logika ako v TrainSystem – biliniárna interpolácia výšky
///   povrchu vo waypointoch + hladké zdieľanie rohových vertexov medzi
///   susednými dlaždicami.
///
/// OBRAT SMERU NA STANICI:
///   path.Reverse() + vehicleDistance = totalLength - vehicleDistance
/// ─────────────────────────────────────────────────────────────────────────
/// </summary>
public class VehicleSystem : MonoBehaviour
{
    public static VehicleSystem instance;

    // =====================================================================
    // POMOCNÁ TRIEDA – PathData
    // Ukladá trasu ako Vector3 body + predpočítané kumulatívne vzdialenosti.
    // (Štruktúra zhodná s TrainSystem.PathData – nezávislá kópia, aby
    // VehicleSystem nezávisel typovo od TrainSystem.)
    // =====================================================================

    public class PathData
    {
        public readonly List<Vector3> points;
        public readonly float[] cumulativeLengths;
        public readonly float totalLength;

        public int Count => points.Count;

        public PathData(List<Vector3> pts)
        {
            points = pts;
            cumulativeLengths = new float[pts.Count];
            cumulativeLengths[0] = 0f;
            for (int i = 1; i < pts.Count; i++)
                cumulativeLengths[i] = cumulativeLengths[i - 1] + Vector3.Distance(pts[i - 1], pts[i]);
            totalLength = pts.Count > 0 ? cumulativeLengths[pts.Count - 1] : 0f;
        }

        /// <summary>
        /// Vráti pozíciu na trase pre zadanú vzdialenosť od začiatku.
        /// Vzdialenosť je upnutá na [0, totalLength].
        /// </summary>
        public Vector3 GetPositionAtDistance(float dist)
        {
            if (points.Count == 0) return Vector3.zero;
            if (points.Count == 1) return points[0];

            dist = Mathf.Clamp(dist, 0f, totalLength);

            int lo = 0, hi = points.Count - 1;
            while (lo < hi - 1)
            {
                int mid = (lo + hi) >> 1;
                if (cumulativeLengths[mid] <= dist) lo = mid;
                else hi = mid;
            }

            float segStart = cumulativeLengths[lo];
            float segEnd = cumulativeLengths[hi];
            float segLen = segEnd - segStart;

            if (segLen < 1e-6f) return points[hi];

            float localT = (dist - segStart) / segLen;
            return Vector3.Lerp(points[lo], points[hi], localT);
        }

        /// <summary>
        /// Vráti normalizovaný smer pohybu na zadanej vzdialenosti.
        /// </summary>
        public Vector3 GetDirectionAtDistance(float dist)
        {
            if (points.Count < 2) return Vector3.forward;

            dist = Mathf.Clamp(dist, 0f, totalLength);

            int lo = 0, hi = points.Count - 1;
            while (lo < hi - 1)
            {
                int mid = (lo + hi) >> 1;
                if (cumulativeLengths[mid] <= dist) lo = mid;
                else hi = mid;
            }

            Vector3 dir = points[hi] - points[lo];
            return dir.sqrMagnitude > 1e-12f ? dir.normalized : Vector3.forward;
        }

        public PathData Reversed()
        {
            var rev = new List<Vector3>(points);
            rev.Reverse();
            return new PathData(rev);
        }
    }

    // =====================================================================
    // DÁTOVÉ ŠTRUKTÚRY
    // =====================================================================

    /// <summary>
    /// Stav životného cyklu vozidla v danom depe – z pohľadu UI.
    ///
    /// Analógia k požadovanému stavu pre vlak. Slúži na to, aby UI okno
    /// (DepotRoadConstructionMenuUI) vedelo rozlíšiť tri situácie a podľa
    /// nich nastaviť StatusTypeTextCaption:
    ///
    ///   • NotCreated – vozidlo v tomto depe ešte nikdy nebolo vytvorené
    ///                  (pred prvým kliknutím na DCCreateVehicleButton).
    ///   • Created    – vozidlo existuje (po DCCreateVehicleButton).
    ///                  StatusTypeTextCaption potom zobrazuje "Running"
    ///                  alebo "Stopped" podľa isRunning.
    ///   • Removed    – vozidlo existovalo, ale bolo zmazané cez
    ///                  DCRemoveVehicleButton.
    ///
    /// Pre StatusTypeTextCaption sa NotCreated aj Removed prejavia rovnako –
    /// textom "NO VEHICLE". Stav je rozlíšený samostatne, lebo neskoršie
    /// UI (detail vozidla) môže obidve situácie odlíšiť, ak bude treba.
    /// </summary>
    public enum VehicleLifecycleState
    {
        NotCreated,
        Created,
        Removed
    }

    public class VehicleData
    {
        public int depotX, depotZ;

        /// <summary>Skrytá kocka (Renderer off) – pohybová logika.</summary>
        public GameObject vehicleObject;

        /// <summary>Vizuálne vozidlo (MAGENTA kváder). 1 kus, žiadne vagóny.</summary>
        public GameObject vehicleBody;

        // ------------------------------------------------------------------
        // DÁTOVÁ ŠTRUKTÚRA VOZIDLA (VehicleStock.cs)
        //
        // VehicleData = pohybový/herný stav (cesta, rýchlosť, stanice...).
        // VehicleInstance = "čo je vozidlo" – nemenné parametre cez Spec
        // (Cost, Speed, Power...) + premenlivý stav kusu (Age,
        // CurrentCapacity). Obidve veci sú zámerne oddelené, presne tak ako
        // TrainData (TrainSystem) vs. TrainInstance (TrainStock).
        //
        // Inštancia vznikne v CreateVehicle a uvoľní sa v RemoveVehicle –
        // viď tieto metódy. UI okno s detailmi vozidla bude tieto údaje
        // len ČÍTAŤ cez vd.vehicleInstance.
        // ------------------------------------------------------------------

        /// <summary>
        /// Dátová štruktúra vozidla (predloha + premenlivý stav).
        /// Nastavená v CreateVehicle, vynulovaná v RemoveVehicle.
        /// </summary>
        public VehicleInstance vehicleInstance;

        // ------------------------------------------------------------------
        // DISTANCE-BASED PATH STATE
        // ------------------------------------------------------------------

        public PathData activePath;
        public float vehicleDistance;
        public float vehicleSpeed;

        // ------------------------------------------------------------------
        // Stavové polia (zhodné s TrainData, ale bez vagónov)
        // ------------------------------------------------------------------
        public List<Vector2Int> stations;
        public int currentStationIndex;
        public bool isRunning;
        public bool isWaiting;
        public bool isReturningToDepot;
        public bool isAtDepot;
        public bool reverseDirection;
        public List<Vector2Int> currentPath;
        public int pathIndex;
        public Vector2Int currentTile;
        public float moveTimer;
        public float waitTimer;
        public bool isComputingPath;
        public List<Vector2Int> pendingPath;
        public bool isGoingToDepotViaStation;
        public bool isStoppedAwaitingDepotReturn;
        public bool pendingReturnToDepot;

        // ------------------------------------------------------------------
        // OBCHOD NA STANICI (VehicleTradeSystem)
        // ------------------------------------------------------------------
        /// <summary>
        /// True, ak v AKTUÁLNEJ zastávke na stanici už prebehol pokus o
        /// transakciu (výmenu tovaru). Bráni tomu, aby sa obchod spustil
        /// každý frame – spustí sa práve raz, po 2 s čakania. Resetuje sa
        /// pri každom novom príchode na cieľovú stanicu (OnPathComplete).
        ///
        /// Analógia k TrainData.tradeDoneAtStation.
        /// </summary>
        public bool tradeDoneAtStation;

        public int pathIndexAtPathStart;
        public int[] waypointToTileIdx;

        public VehicleData(int dx, int dz)
        {
            depotX = dx; depotZ = dz;
            stations = new List<Vector2Int>();
            currentStationIndex = 0;
            isRunning = false; isWaiting = false;
            isReturningToDepot = false; isAtDepot = true;
            reverseDirection = false;
            currentPath = new List<Vector2Int>();
            pathIndex = 0;
            currentTile = new Vector2Int(dx, dz);
            moveTimer = 0f; waitTimer = 0f;
            isComputingPath = false;
            pendingPath = null;
            isGoingToDepotViaStation = false;
            isStoppedAwaitingDepotReturn = false;
            pendingReturnToDepot = false;
            tradeDoneAtStation = false;

            activePath = null;
            vehicleDistance = 0f;
            vehicleSpeed = 0f;
            pathIndexAtPathStart = 0;
            waypointToTileIdx = null;
        }
    }

    // =====================================================================
    // VÝSLEDKOVÁ FRONTA
    // =====================================================================

    enum PathDestination { Station, Depot, ReturnViaStation }

    struct PathResult
    {
        public int depotKey;
        public List<Vector2Int> path;
        public bool isReturnToDepot;
        public PathDestination destination;
    }

    public enum ReturnToDepotResult { Dispatched, StoppedAwaitingSecondR, Error }

    readonly System.Collections.Concurrent.ConcurrentQueue<PathResult> _pendingPathResults
        = new System.Collections.Concurrent.ConcurrentQueue<PathResult>();

    // =====================================================================
    // DEBOUNCE
    // =====================================================================

    const float DEBOUNCE_DELAY = 0.4f;
    bool _mapChangePending = false;
    float _mapChangedDebounceTimer = 0f;

    // =====================================================================
    // SNAPSHOT ROAD GRIDU
    // =====================================================================

    const int GRID_SIZE = 256;

    /// <summary>
    /// Snapshot road časti tileGrid. Pre tiles, ktoré nie sú ROAD kategórie,
    /// uloží tileID=0 → A* ich pokladá za nepriechodné. Tým je VehicleSystem
    /// úplne izolovaný od RAIL grafu, hoci obidva systémy zdieľajú jeden
    /// tile grid v IndicatrixAPI.
    /// </summary>
    struct TileGridSnapshot
    {
        public int[] tileIDs;
        public int[] connections;
    }

    TileGridSnapshot SnapshotTileGrid()
    {
        var snap = new TileGridSnapshot
        {
            tileIDs = new int[GRID_SIZE * GRID_SIZE],
            connections = new int[GRID_SIZE * GRID_SIZE]
        };

        for (int x = 0; x < GRID_SIZE; x++)
        {
            for (int z = 0; z < GRID_SIZE; z++)
            {
                // GetTileByIndexAny vidí všetky tiles bez ohľadu na kategóriu;
                // následne filtrujeme len ROAD do snapshotu.
                var td = IndicatrixAPI.instance.GetTileByIndexAny(x, z);
                int idx = z * GRID_SIZE + x;
                if (td.category == IndicatrixAPI.TileCategory.Road)
                {
                    snap.tileIDs[idx] = td.tileID;
                    snap.connections[idx] = (int)td.connections;
                }
                else
                {
                    // Ne-ROAD tiles sa do road snapshotu nezapočítavajú.
                    snap.tileIDs[idx] = 0;
                    snap.connections[idx] = 0;
                }
            }
        }
        return snap;
    }

    // =====================================================================
    // KONŠTANTY A STAV
    // =====================================================================

    Dictionary<int, VehicleData> vehicles = new Dictionary<int, VehicleData>();

    /// <summary>
    /// Stav životného cyklu vozidla per depo.
    ///
    /// Prečo samostatný slovník a nie pole na VehicleData:
    /// VehicleData v `vehicles` existuje IBA kým vozidlo existuje – po
    /// RemoveVehicle sa záznam z `vehicles` odstráni. Stav "Removed" sa
    /// teda nemá kam uložiť. Tento slovník prežíva odstránenie vozidla a
    /// pamätá si, či vozidlo v danom depe ešte nebolo vytvorené
    /// (depo v slovníku chýba → NotCreated) alebo bolo zmazané (Removed).
    /// </summary>
    Dictionary<int, VehicleLifecycleState> vehicleLifecycle
        = new Dictionary<int, VehicleLifecycleState>();

    /// <summary>
    /// Vráti stav životného cyklu vozidla pre dané depo.
    /// Ak depo nie je v evidencii, vozidlo ešte nikdy nebolo vytvorené.
    /// </summary>
    public VehicleLifecycleState GetVehicleLifecycleState(int dx, int dz)
    {
        int key = DepotKey(dx, dz);
        return vehicleLifecycle.TryGetValue(key, out var state)
            ? state
            : VehicleLifecycleState.NotCreated;
    }

    /// <summary>Čas prechodu jednej dlaždice v sekundách.</summary>
    const float MOVE_TIME = 2.0f;
    const float STATION_WAIT = 10.0f;
    const float BREAK_WAIT = 1.0f;

    /// <summary>
    /// Oneskorenie od príchodu na cieľovú stanicu, po ktorom sa vykoná
    /// výmena tovaru (VehicleTradeSystem). Zadanie: štandardné čakanie je
    /// 10 s (STATION_WAIT), transakcia prebehne po 2 s od príchodu.
    ///
    /// Spúšťacia podmienka v UpdateVehicle:
    /// (STATION_WAIT - waitTimer) >= TRADE_DELAY, t.j. po 2 s čakania.
    /// </summary>
    const float TRADE_DELAY = 2.0f;

    /// <summary>Rozmery kvádra vozidla.</summary>
    static readonly Vector3 VEHICLE_SCALE = new Vector3(0.3f, 0.3f, 0.7f);

    void Awake()
    {
        instance = this;
    }

    // =====================================================================
    // POMOCNÉ
    // =====================================================================

    int DepotKey(int x, int z) => x * 10000 + z;

    Vector3 TileCenter(Vector2Int tile)
    {
        float y = GetTileSurfaceY(tile, 0.5f, 0.5f) + ROAD_OFFSET_Y;
        return new Vector3(tile.x + 0.5f, y, tile.y + 0.5f);
    }

    float GetTerrainY(int x, int z)
    {
        try
        {
            int width = TerrainManager.instance.terrainWidth + 1;
            int index = z * width + x;
            if (index >= 0 && index < TerrainManager.instance.coordsF.Length)
                return TerrainManager.instance.coordsF[index].y;
        }
        catch { }
        return 0f;
    }

    /// <summary>Vertikálny offset vozovky nad povrchom terénu.</summary>
    const float ROAD_OFFSET_Y = 0.3f;

    /// <summary>
    /// Biliniárna interpolácia výšky terénu vnútri jednej dlaždice medzi
    /// 4 rohovými vertexmi. Logika zhodná s TrainSystem.GetTileSurfaceY.
    ///
    /// PREČO TOTO POTREBUJEME (kopce, LevelUp / LevelDown):
    ///   Bez tejto interpolácie by vozidlo na svahu ostalo vodorovne a
    ///   na hrane medzi dlaždicami "skočilo" na novú výšku → 90° schod.
    ///   S biliniárnou interpoláciou má každý waypoint správnu výšku na
    ///   šikmej ploche dlaždice → pohyb cez svah je plynulý.
    ///
    /// HLADKOSŤ NA HRANÁCH MEDZI DLAŽDICAMI:
    ///   A.right-edge a B.left-edge zdieľajú ten istý pár rohových vertexov,
    ///   takže interpolovaná Y vychádza identická → žiadny schod, žiadny lom.
    /// </summary>
    float GetTileSurfaceY(Vector2Int tile, float u, float v)
    {
        float h00 = GetTerrainY(tile.x, tile.y);
        float h10 = GetTerrainY(tile.x + 1, tile.y);
        float h01 = GetTerrainY(tile.x, tile.y + 1);
        float h11 = GetTerrainY(tile.x + 1, tile.y + 1);

        float omu = 1f - u;
        float omv = 1f - v;

        return omu * omv * h00
             + u * omv * h10
             + omu * v * h01
             + u * v * h11;
    }

    /// <summary>
    /// Vráti tileID pre danú ROAD dlaždicu. Pre tiles, ktoré nie sú ROAD
    /// kategórie (alebo sú mimo gridu), vracia -1 / 0. Slúži ako sentinel
    /// pre VehicleSystem – RAIL tiles sú pre nás "neviditeľné".
    /// </summary>
    int GetRoadTileID(int x, int z)
    {
        if (x < 0 || x >= GRID_SIZE || z < 0 || z >= GRID_SIZE) return -1;
        var td = IndicatrixAPI.instance.GetTileByIndexAny(x, z);
        if (td.category != IndicatrixAPI.TileCategory.Road) return 0;
        return td.tileID;
    }

    bool IsPassable(int x, int z)
    {
        int id = GetRoadTileID(x, z);
        return id == 1 || id == 2 || id == 3;
    }

    // =====================================================================
    // DIRECTION HELPERS – mapovanie Vector2Int <-> DirectionMask
    // =====================================================================

    static IndicatrixAPI.DirectionMask DirFromStep(Vector2Int step)
    {
        if (step.x == 1 && step.y == 0) return IndicatrixAPI.DirectionMask.Right;
        if (step.x == -1 && step.y == 0) return IndicatrixAPI.DirectionMask.Left;
        if (step.x == 0 && step.y == 1) return IndicatrixAPI.DirectionMask.Top;
        if (step.x == 0 && step.y == -1) return IndicatrixAPI.DirectionMask.Bottom;
        return IndicatrixAPI.DirectionMask.None;
    }

    static IndicatrixAPI.DirectionMask GetDirection(Vector2Int from, Vector2Int to)
    {
        return DirFromStep(to - from);
    }

    /// <summary>
    /// Pre danú dlaždicu (x, z) a smer 'dir' vráti svetový bod uprostred
    /// danej hrany dlaždice. Logika zhodná s TrainSystem.EdgePoint, len
    /// s ROAD_OFFSET_Y namiesto TRACK_OFFSET_Y.
    /// </summary>
    Vector3 EdgePoint(Vector2Int tile, IndicatrixAPI.DirectionMask dir)
    {
        float fx = tile.x, fz = tile.y;
        switch (dir)
        {
            case IndicatrixAPI.DirectionMask.Right:
                return new Vector3(fx + 1.0f, GetTileSurfaceY(tile, 1.0f, 0.5f) + ROAD_OFFSET_Y, fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Left:
                return new Vector3(fx + 0.0f, GetTileSurfaceY(tile, 0.0f, 0.5f) + ROAD_OFFSET_Y, fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Top:
                return new Vector3(fx + 0.5f, GetTileSurfaceY(tile, 0.5f, 1.0f) + ROAD_OFFSET_Y, fz + 1.0f);
            case IndicatrixAPI.DirectionMask.Bottom:
                return new Vector3(fx + 0.5f, GetTileSurfaceY(tile, 0.5f, 0.0f) + ROAD_OFFSET_Y, fz + 0.0f);
            default: return TileCenter(tile);
        }
    }

    /// <summary>
    /// Krivka (Road) = presne 2 nastavené smery, ktoré NIE SÚ proti-smery.
    /// </summary>
    static bool IsCurveTile(IndicatrixAPI.DirectionMask conns)
    {
        int bits = 0;
        if ((conns & IndicatrixAPI.DirectionMask.Left) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Right) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Top) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Bottom) != 0) bits++;
        if (bits != 2) return false;

        bool horiz = (conns & (IndicatrixAPI.DirectionMask.Left | IndicatrixAPI.DirectionMask.Right))
                       == (IndicatrixAPI.DirectionMask.Left | IndicatrixAPI.DirectionMask.Right);
        bool vert = (conns & (IndicatrixAPI.DirectionMask.Top | IndicatrixAPI.DirectionMask.Bottom))
                       == (IndicatrixAPI.DirectionMask.Top | IndicatrixAPI.DirectionMask.Bottom);

        return !horiz && !vert;
    }

    /// <summary>
    /// T-križovatka / Y-rozdvojenie (RoadSwitch) – 3 spojenia, plne obojsmerné.
    /// </summary>
    static bool IsSwitchTile(IndicatrixAPI.DirectionMask conns)
    {
        int bits = 0;
        if ((conns & IndicatrixAPI.DirectionMask.Left) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Right) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Top) != 0) bits++;
        if ((conns & IndicatrixAPI.DirectionMask.Bottom) != 0) bits++;
        return bits == 3;
    }

    /// <summary>
    /// Križovatka (RoadCrossroad) – všetky 4 smery.
    /// </summary>
    static bool IsCrossroadTile(IndicatrixAPI.DirectionMask conns)
    {
        const IndicatrixAPI.DirectionMask ALL =
            IndicatrixAPI.DirectionMask.Left
          | IndicatrixAPI.DirectionMask.Right
          | IndicatrixAPI.DirectionMask.Top
          | IndicatrixAPI.DirectionMask.Bottom;
        return (conns & ALL) == ALL;
    }

    /// <summary>
    /// True ak sú dva smery navzájom kolmé (jeden horizontálny + jeden vertikálny).
    /// </summary>
    static bool IsPerpendicular(IndicatrixAPI.DirectionMask a, IndicatrixAPI.DirectionMask b)
    {
        bool aHoriz = a == IndicatrixAPI.DirectionMask.Left || a == IndicatrixAPI.DirectionMask.Right;
        bool aVert = a == IndicatrixAPI.DirectionMask.Top || a == IndicatrixAPI.DirectionMask.Bottom;
        bool bHoriz = b == IndicatrixAPI.DirectionMask.Left || b == IndicatrixAPI.DirectionMask.Right;
        bool bVert = b == IndicatrixAPI.DirectionMask.Top || b == IndicatrixAPI.DirectionMask.Bottom;
        return (aHoriz && bVert) || (aVert && bHoriz);
    }

    /// <summary>
    /// Vnútorný bod pre 45° turnout / rohový prejazd. Zhodné s TrainSystem.
    /// </summary>
    Vector3 SwitchInnerEdgePoint(Vector2Int tile, IndicatrixAPI.DirectionMask dir, float t)
    {
        float fx = tile.x, fz = tile.y;
        switch (dir)
        {
            case IndicatrixAPI.DirectionMask.Right:
                return new Vector3(fx + 1.0f - t,
                    GetTileSurfaceY(tile, 1.0f - t, 0.5f) + ROAD_OFFSET_Y,
                    fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Left:
                return new Vector3(fx + 0.0f + t,
                    GetTileSurfaceY(tile, 0.0f + t, 0.5f) + ROAD_OFFSET_Y,
                    fz + 0.5f);
            case IndicatrixAPI.DirectionMask.Top:
                return new Vector3(fx + 0.5f,
                    GetTileSurfaceY(tile, 0.5f, 1.0f - t) + ROAD_OFFSET_Y,
                    fz + 1.0f - t);
            case IndicatrixAPI.DirectionMask.Bottom:
                return new Vector3(fx + 0.5f,
                    GetTileSurfaceY(tile, 0.5f, 0.0f + t) + ROAD_OFFSET_Y,
                    fz + 0.0f + t);
            default: return TileCenter(tile);
        }
    }

    // =====================================================================
    // CAN MOVE – validácia smeru pre A* (thread-safe, snapshot)
    // =====================================================================

    /// <summary>
    /// Validácia A* prechodu z 'fromTile' na 'toTile' v smere 'direction'.
    /// Identické pravidlá ako TrainSystem.CanMove – pracuje so snapshot
    /// poľami, ktoré pre VehicleSystem obsahujú IBA ROAD tiles (RAIL tiles
    /// majú tileID=0 v snapshot, takže sú nepriechodné).
    /// </summary>
    static bool CanMove(int[] tileIDs, int[] conns, Vector2Int fromTile, Vector2Int toTile,
                        IndicatrixAPI.DirectionMask direction)
    {
        if (toTile.x < 0 || toTile.x >= GRID_SIZE || toTile.y < 0 || toTile.y >= GRID_SIZE)
            return false;
        if (fromTile.x < 0 || fromTile.x >= GRID_SIZE || fromTile.y < 0 || fromTile.y >= GRID_SIZE)
            return false;

        int toIdx = toTile.y * GRID_SIZE + toTile.x;
        int toID = tileIDs[toIdx];
        if (!(toID == 1 || toID == 2 || toID == 3)) return false;

        int fromIdx = fromTile.y * GRID_SIZE + fromTile.x;
        IndicatrixAPI.DirectionMask fromConn = (IndicatrixAPI.DirectionMask)conns[fromIdx];
        IndicatrixAPI.DirectionMask toConn = (IndicatrixAPI.DirectionMask)conns[toIdx];

        if ((fromConn & direction) == 0) return false;

        IndicatrixAPI.DirectionMask oppositeDir = IndicatrixAPI.Opposite(direction);
        if ((toConn & oppositeDir) == 0) return false;

        return true;
    }

    // =====================================================================
    // BUILD WAYPOINT PATH – konvertuje List<Vector2Int> na waypointy
    // (logika identická s TrainSystem.BuildWaypointPath – pozri tam podrobné
    //  komentáre k pravidlám pre krivky, výhybky, križovatky a tile-boundary
    //  semantike)
    // =====================================================================

    void BuildWaypointPath(List<Vector2Int> tilePath, out List<Vector3> waypoints,
                           out List<int> tileIdxPerWaypoint)
    {
        var wp = new List<Vector3>();
        var tIdx = new List<int>();

        if (tilePath == null || tilePath.Count == 0)
        {
            waypoints = wp;
            tileIdxPerWaypoint = tIdx;
            return;
        }

        if (tilePath.Count == 1)
        {
            wp.Add(TileCenter(tilePath[0]));
            tIdx.Add(0);
            waypoints = wp;
            tileIdxPerWaypoint = tIdx;
            return;
        }

        // Lokálna funkcia – deduplikuje len waypointy z rovnakej dlaždice
        // (intra-tile duplikáty). Hraničné waypointy A.exitEdge a B.entryEdge
        // sa zachovajú ako 2 samostatné waypointy aj keď sa v priestore kryjú.
        void AddWaypoint(Vector3 pt, int tileIdx)
        {
            if (wp.Count > 0)
            {
                Vector3 last = wp[wp.Count - 1];
                int lastTileIdx = tIdx[tIdx.Count - 1];
                bool sameTile = (lastTileIdx == tileIdx);
                bool samePos = (pt - last).sqrMagnitude < 1e-8f;
                if (sameTile && samePos) return;
            }
            wp.Add(pt);
            tIdx.Add(tileIdx);
        }

        for (int i = 0; i < tilePath.Count; i++)
        {
            Vector2Int tile = tilePath[i];
            var conns = IndicatrixAPI.instance.GetTileByIndexAny(tile.x, tile.y).connections;

            bool isFirst = (i == 0);
            bool isLast = (i == tilePath.Count - 1);

            IndicatrixAPI.DirectionMask outDir = IndicatrixAPI.DirectionMask.None;
            if (!isLast) outDir = GetDirection(tile, tilePath[i + 1]);

            IndicatrixAPI.DirectionMask inDir = IndicatrixAPI.DirectionMask.None;
            if (!isFirst) inDir = IndicatrixAPI.Opposite(GetDirection(tilePath[i - 1], tile));

            // ── KRIVKA: entryEdge → exitEdge (priama diagonála ~45°)
            if (!isFirst && !isLast && IsCurveTile(conns))
            {
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
                continue;
            }

            // ── T-KRIŽOVATKA (RoadSwitch) – 3 spojenia
            if (!isFirst && !isLast && IsSwitchTile(conns))
            {
                if (IsPerpendicular(inDir, outDir))
                {
                    const float SWITCH_TURNOUT_T = 0.25f;
                    AddWaypoint(EdgePoint(tile, inDir), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, inDir, SWITCH_TURNOUT_T), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, outDir, SWITCH_TURNOUT_T), i);
                    AddWaypoint(EdgePoint(tile, outDir), i);
                    continue;
                }

                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
                continue;
            }

            // ── KRIŽOVATKA (RoadCrossroad) – 4 spojenia
            if (!isFirst && !isLast && IsCrossroadTile(conns))
            {
                if (IsPerpendicular(inDir, outDir))
                {
                    const float CROSSROAD_TURNOUT_T = 0.25f;
                    AddWaypoint(EdgePoint(tile, inDir), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, inDir, CROSSROAD_TURNOUT_T), i);
                    AddWaypoint(SwitchInnerEdgePoint(tile, outDir, CROSSROAD_TURNOUT_T), i);
                    AddWaypoint(EdgePoint(tile, outDir), i);
                    continue;
                }

                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
                continue;
            }

            // ── PRIAMA / KONCOVÁ DLAŽDICA
            if (isFirst)
            {
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
            }
            else if (isLast)
            {
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
            }
            else
            {
                AddWaypoint(EdgePoint(tile, inDir), i);
                AddWaypoint(TileCenter(tile), i);
                AddWaypoint(EdgePoint(tile, outDir), i);
            }
        }

        waypoints = wp;
        tileIdxPerWaypoint = tIdx;
    }

    // =====================================================================
    // VOZIDLO – pomocná metóda vytvorenia kvádra
    // =====================================================================

    GameObject CreateVehiclePart(string name, Color color, Vector3 position)
    {
        GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
        go.name = name;
        go.transform.localScale = VEHICLE_SCALE;
        go.transform.position = position;
        Destroy(go.GetComponent<BoxCollider>());
        var rend = go.GetComponent<Renderer>();
        rend.material = new Material(Shader.Find("Standard"));
        rend.material.color = color;
        go.SetActive(false);
        return go;
    }

    // =====================================================================
    // SPRÁVA VOZIDIEL
    // =====================================================================

    /// <summary>
    /// Vytvorí vozidlo v zadanom ROAD depe.
    ///
    /// Parameter vehicleTypeName je názov typu vozidla z VehicleCatalog
    /// (napr. "Vehicle 1", "Vehicle 2"). Slúži na dve veci:
    ///   1. Vyhľadanie VehicleSpec v katalógu → vytvorenie VehicleInstance
    ///      (dátová štruktúra vozidla s atribútmi Cost, Speed, Power...).
    ///   2. Pomenovanie vizuálneho GameObjectu.
    /// Vizuálne ide stále o ten istý magenta kváder – atribúty žijú v
    /// dátovej štruktúre vd.vehicleInstance, nie vo vizuáli.
    /// </summary>
    public bool CreateVehicle(int dx, int dz, string vehicleTypeName = "Vehicle 1")
    {
        int key = DepotKey(dx, dz);
        if (vehicles.ContainsKey(key)) return false;
        if (GetRoadTileID(dx, dz) != 3) return false;

        // Vyhľadáme predlohu vozidla v katalógu. Ak názov nesedí (napr.
        // neznámy typ), spadneme na prvý typ v katalógu, aby vozidlo malo
        // vždy platnú dátovú štruktúru.
        VehicleSpec spec = VehicleCatalog.ByName(vehicleTypeName)
                           ?? VehicleCatalog.ByIndex(0);
        if (spec == null)
        {
            Debug.LogError("[VehicleSystem] VehicleCatalog je prázdny – vozidlo sa nedá vytvoriť.");
            return false;
        }

        VehicleData vd = new VehicleData(dx, dz);
        Vector3 depotPos = TileCenter(new Vector2Int(dx, dz));

        // Dátová štruktúra vozidla – nová inštancia z katalógovej predlohy.
        // Age = 0, CurrentCapacity = 0 (nastaví konštruktor VehicleInstance).
        vd.vehicleInstance = new VehicleInstance(spec);

        // Skrytá kocka – pohybová logika
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.localScale = VEHICLE_SCALE;
        cube.transform.position = depotPos;
        cube.name = $"VehicleHead_{dx}_{dz}";
        Destroy(cube.GetComponent<BoxCollider>());
        cube.GetComponent<Renderer>().enabled = false;
        vd.vehicleObject = cube;

        // Vozidlo – MAGENTA, 1 kus, žiadne vagóny
        string bodyName = $"{spec.Name}_{dx}_{dz}";
        vd.vehicleBody = CreateVehiclePart(bodyName, new Color(1f, 0f, 1f), depotPos);

        vehicles[key] = vd;

        // Životný cyklus → Created (StatusTypeTextCaption potom zobrazí
        // Running/Stopped namiesto "NO VEHICLE").
        vehicleLifecycle[key] = VehicleLifecycleState.Created;

        Debug.Log($"[VehicleSystem] Vozidlo '{spec.Name}' ({spec.Type}) vytvorené v depe [{dx},{dz}]. " +
                  $"Kapacita {spec.MaximumCapacity}, cena {spec.Cost}, rýchlosť {spec.Speed}.");
        return true;
    }

    public VehicleData GetVehicle(int dx, int dz)
    {
        int key = DepotKey(dx, dz);
        return vehicles.TryGetValue(key, out var vd) ? vd : null;
    }

    /// <summary>
    /// Vráti súradnice [depotX, depotZ] VŠETKÝCH aktuálne existujúcich
    /// vozidlových dep. Keďže platí "1 vehicle depo = 1 vehicle", počet
    /// prvkov zoznamu = počet vozidiel v hre.
    ///
    /// Slúži pre informačné UI (StatusStationsMenuUI). Vnútorný slovník
    /// `vehicles` ostáva privátny – navonok dávame len read-only kópiu
    /// kľúčových údajov, takže volajúci nemôže poškodiť interný stav.
    /// </summary>
    public List<Vector2Int> GetAllDepotCoords()
    {
        var result = new List<Vector2Int>(vehicles.Count);
        foreach (var kvp in vehicles)
        {
            VehicleData vd = kvp.Value;
            result.Add(new Vector2Int(vd.depotX, vd.depotZ));
        }
        return result;
    }

    /// <summary>
    /// Celkový počet vozidlových dep (= počet vozidiel). Pohodlný getter,
    /// aby UI nemuselo kvôli počtu vytvárať celý zoznam cez
    /// GetAllDepotCoords().
    /// </summary>
    public int GetDepotCount()
    {
        return vehicles.Count;
    }

    /// <summary>
    /// Vráti dátové štruktúry (VehicleInstance) VŠETKÝCH aktuálne
    /// existujúcich vozidiel. Keďže platí "1 vehicle depo = 1 vehicle",
    /// počet prvkov zoznamu = počet vozidiel v hre.
    ///
    /// Slúži pre informačné UI (StatusVehiclesMenuUI), ktoré z každej
    /// VehicleInstance prečíta názov vozidla (Name) a typ suroviny
    /// (Type.Name).
    ///
    /// Vnútorný slovník `vehicles` ostáva privátny – navonok dávame len
    /// read-only kópiu zoznamu referencií na VehicleInstance. Volajúci tak
    /// nemôže pridať/odobrať vozidlo (zmeniť `vehicles`), čítať atribúty
    /// jednotlivých vozidiel ale môže. Analógia k GetAllDepotCoords().
    /// </summary>
    public List<VehicleInstance> GetAllVehicleInstances()
    {
        var result = new List<VehicleInstance>(vehicles.Count);
        foreach (var kvp in vehicles)
        {
            VehicleData vd = kvp.Value;
            if (vd != null && vd.vehicleInstance != null)
                result.Add(vd.vehicleInstance);
        }
        return result;
    }

    /// <summary>
    /// EKONOMIKA (read-only snapshot): pre KAŽDÉ existujúce vozidlo spáruje jeho
    /// DEPO súradnice [depotX, depotZ] (= kľúč pre <see cref="ReturnToDepot"/>)
    /// s jeho dátovou inštanciou <see cref="VehicleInstance"/> (OperatingCosts,
    /// ServiceLife, ServicingInterval). Interný slovník `vehicles` ostáva privátny.
    ///
    /// Slúži pre EconomySystem: mesačné prevádzkové náklady + automatické
    /// poslanie vozidla do depa po uplynutí servisného intervalu alebo životnosti.
    /// Analógia k TrainSystem.GetActiveTrainInfos().
    /// </summary>
    public List<ActiveVehicleInfo> GetActiveVehicleInfos()
    {
        var result = new List<ActiveVehicleInfo>(vehicles.Count);
        foreach (var kvp in vehicles)
        {
            VehicleData vd = kvp.Value;
            if (vd != null && vd.vehicleInstance != null)
                result.Add(new ActiveVehicleInfo(vd.depotX, vd.depotZ, vd.vehicleInstance));
        }
        return result;
    }

    /// <summary>Read-only dvojica [depo súradnice + dátová inštancia vozidla] pre EconomySystem.</summary>
    public readonly struct ActiveVehicleInfo
    {
        public readonly int DepotX;
        public readonly int DepotZ;
        public readonly VehicleInstance Instance;

        public ActiveVehicleInfo(int depotX, int depotZ, VehicleInstance instance)
        {
            DepotX = depotX;
            DepotZ = depotZ;
            Instance = instance;
        }
    }

    public bool AddStation(int depotX, int depotZ, int stX, int stZ)
    {
        VehicleData vd = GetVehicle(depotX, depotZ);
        if (vd == null) return false;
        if (GetRoadTileID(stX, stZ) != 2) return false;

        Vector2Int st = new Vector2Int(stX, stZ);
        if (!vd.stations.Contains(st))
        {
            vd.stations.Add(st);
            Debug.Log($"[VehicleSystem] Stanica [{stX},{stZ}] pridaná pre vozidlo v depe [{depotX},{depotZ}]. Celkom: {vd.stations.Count}");
        }
        return true;
    }

    public bool StartVehicle(int dx, int dz)
    {
        VehicleData vd = GetVehicle(dx, dz);
        if (vd == null) return false;
        if (vd.stations.Count < 2)
        {
            Debug.LogWarning($"[VehicleSystem] Vozidlo v depe [{dx},{dz}] nemá aspoň 2 stanice!");
            return false;
        }

        bool isResume = !vd.isAtDepot && vd.currentPath != null && vd.currentPath.Count > 0;

        vd.isRunning = true;
        vd.isWaiting = false;
        vd.isReturningToDepot = false;

        if (isResume)
        {
            Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] OBNOVENÉ (resume).");
        }
        else
        {
            vd.isAtDepot = false;
            vd.currentStationIndex = 0;
            vd.reverseDirection = false;
            vd.moveTimer = 0f;
            vd.waitTimer = 0f;
            vd.pendingPath = null;
            vd.pendingReturnToDepot = false;

            vd.activePath = null;
            vd.vehicleDistance = 0f;

            ComputeNextPathAsync(vd);
            Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] SPUSTENÉ (prvý štart).");
        }

        return true;
    }

    public bool StopVehicle(int dx, int dz)
    {
        VehicleData vd = GetVehicle(dx, dz);
        if (vd == null) return false;
        vd.isRunning = false;
        vd.isComputingPath = false;
        vd.pendingPath = null;
        vd.pendingReturnToDepot = false;
        Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] ZASTAVENÉ.");
        return true;
    }

    public ReturnToDepotResult ReturnToDepot(int dx, int dz)
    {
        VehicleData vd = GetVehicle(dx, dz);
        if (vd == null) return ReturnToDepotResult.Error;

        if (GetRoadTileID(dx, dz) != 3)
        {
            Debug.LogWarning($"[VehicleSystem] Depo [{dx},{dz}] neexistuje – vozidlo zastane.");
            vd.isRunning = false;
            vd.isStoppedAwaitingDepotReturn = false;
            return ReturnToDepotResult.Error;
        }

        if (vd.isStoppedAwaitingDepotReturn)
        {
            vd.isStoppedAwaitingDepotReturn = false;
            vd.pendingReturnToDepot = false;
            vd.isRunning = true;
            vd.isWaiting = false;
            vd.waitTimer = 0f;
            vd.isGoingToDepotViaStation = false;
            vd.isComputingPath = false;
            vd.pendingPath = null;
            DispatchToDepot(vd);
            Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] → depo priamo.");
            return ReturnToDepotResult.Dispatched;
        }

        Vector2Int depotTile = new Vector2Int(dx, dz);

        if (vd.isWaiting)
        {
            vd.pendingReturnToDepot = false;
            vd.isGoingToDepotViaStation = true;
            vd.isStoppedAwaitingDepotReturn = false;
            Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] čaká na stanici → po odpočítaní pôjde do depa.");
            return ReturnToDepotResult.Dispatched;
        }

        if (IsDepotOnCurrentPath(vd, depotTile))
        {
            vd.pendingReturnToDepot = false;
            vd.isGoingToDepotViaStation = false;
            vd.isStoppedAwaitingDepotReturn = false;
            TrimPathToDepot(vd, depotTile);
            vd.isReturningToDepot = true;
            RebuildActivePathFromCurrentPath(vd);
            Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] → depo PRIAMO (na aktuálnej ceste).");
            return ReturnToDepotResult.Dispatched;
        }

        vd.pendingReturnToDepot = true;
        vd.isGoingToDepotViaStation = false;
        vd.isStoppedAwaitingDepotReturn = false;
        Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}]: depo nie je v smere jazdy → dokončí cestu a potom pôjde do depa.");
        return ReturnToDepotResult.Dispatched;
    }

    bool IsDepotOnCurrentPath(VehicleData vd, Vector2Int depotTile)
    {
        if (vd.currentPath == null || vd.pathIndex >= vd.currentPath.Count) return false;
        for (int i = vd.pathIndex; i < vd.currentPath.Count; i++)
            if (vd.currentPath[i] == depotTile) return true;
        return false;
    }

    void TrimPathToDepot(VehicleData vd, Vector2Int depotTile)
    {
        if (vd.currentPath == null) return;
        for (int i = vd.pathIndex; i < vd.currentPath.Count; i++)
        {
            if (vd.currentPath[i] == depotTile)
            {
                vd.currentPath = vd.currentPath.GetRange(0, i + 1);
                return;
            }
        }
    }

    void DispatchToDepot(VehicleData vd)
    {
        int key = DepotKey(vd.depotX, vd.depotZ);
        Vector2Int goal = new Vector2Int(vd.depotX, vd.depotZ);
        Vector2Int start = vd.currentTile;
        var snap = SnapshotTileGrid();

        vd.isReturningToDepot = true;
        vd.isGoingToDepotViaStation = false;
        vd.isComputingPath = true;

        Task.Run(() =>
        {
            var path = AStarPathThreaded(snap, start, goal);
            _pendingPathResults.Enqueue(new PathResult
            {
                depotKey = key,
                path = path,
                isReturnToDepot = true,
                destination = PathDestination.Depot
            });
        });

        Debug.Log($"[VehicleSystem] Vozidlo z depa [{vd.depotX},{vd.depotZ}] → depo.");
    }

    /// <summary>
    /// Odstráni vozidlo. Povolené iba ak je vozidlo fyzicky v depe a zastavené.
    /// Logika zhodná s TrainSystem.RemoveTrain (pozri tam podrobný popis
    /// scenárov, ktoré vyžadujú túto guardu).
    ///
    /// ZRKADLOVOSŤ s CreateVehicle:
    /// CreateVehicle vytvorí dátové štruktúry vozidla (VehicleInstance) a
    /// vizuálne GameObjecty. RemoveVehicle musí to isté uvoľniť – teda
    /// okrem zničenia GameObjectov aj uvoľniť vd.vehicleInstance a
    /// nastaviť životný cyklus na Removed.
    /// </summary>
    public bool RemoveVehicle(int dx, int dz)
    {
        VehicleData vd = GetVehicle(dx, dz);
        if (vd == null) return false;

        if (vd.isRunning)
        {
            Debug.LogWarning($"[VehicleSystem] Vozidlo v depe [{dx},{dz}] sa nedá odstrániť – stále beží. Najprv ho zastavte.");
            return false;
        }

        Vector2Int depotTile = new Vector2Int(dx, dz);
        if (vd.currentTile != depotTile)
        {
            Debug.LogWarning($"[VehicleSystem] Vozidlo v depe [{dx},{dz}] sa nedá odstrániť – nie je fyzicky v depe (aktuálne na [{vd.currentTile.x},{vd.currentTile.y}]).");
            return false;
        }

        if (GetRoadTileID(dx, dz) != 3)
        {
            Debug.LogWarning($"[VehicleSystem] Tile [{dx},{dz}] už nie je ROAD depo – vozidlo sa nedá odstrániť.");
            return false;
        }

        vd.isComputingPath = false;
        vd.pendingPath = null;

        if (vd.vehicleObject != null) Destroy(vd.vehicleObject);
        if (vd.vehicleBody != null) Destroy(vd.vehicleBody);

        // Uvoľnenie dátovej štruktúry vozidla – zrkadlovo k CreateVehicle,
        // ktorý ju vytvoril. Po tomto bode vozidlo nemá žiadne atribúty.
        vd.vehicleInstance = null;

        int key = DepotKey(dx, dz);
        vehicles.Remove(key);

        // Životný cyklus → Removed (StatusTypeTextCaption potom zobrazí
        // "NO VEHICLE", kým sa vozidlo znovu nevytvorí cez DCCreateVehicleButton).
        vehicleLifecycle[key] = VehicleLifecycleState.Removed;

        Debug.Log($"[VehicleSystem] Vozidlo z depa [{dx},{dz}] ODSTRÁNENÉ (dátová štruktúra uvoľnená).");
        return true;
    }

    // =====================================================================
    // NOTIFIKÁCIA PRI ZMENE MAPY – DEBOUNCE
    // =====================================================================

    public void OnMapChanged()
    {
        _mapChangePending = true;
        _mapChangedDebounceTimer = DEBOUNCE_DELAY;
    }

    // =====================================================================
    // UPDATE
    // =====================================================================

    void Update()
    {
        if (_mapChangePending)
        {
            _mapChangedDebounceTimer -= Time.deltaTime;
            if (_mapChangedDebounceTimer <= 0f)
            {
                _mapChangePending = false;
                _mapChangedDebounceTimer = 0f;
                TriggerMapChangedReroute();
            }
        }

        ApplyPendingPathResults();

        foreach (var kvp in vehicles)
        {
            VehicleData vd = kvp.Value;
            if (vd.vehicleObject == null) continue;
            if (!vd.isRunning) continue;
            UpdateVehicle(vd);
        }
    }

    // =====================================================================
    // POHYB VOZIDLA – DISTANCE-BASED
    // =====================================================================

    void UpdateVehicle(VehicleData vd)
    {
        // ── Čakanie (stanica / pauza)
        if (vd.isWaiting)
        {
            vd.waitTimer -= Time.deltaTime;

            // ── OBCHOD: po 2 s čakania na cieľovej stanici ────────────────
            // Spustí sa práve raz za zastávku. tradeDoneAtStation je nastavené
            // na false iba pri príchode na cieľovú stanicu (OnPathComplete),
            // takže pri BREAK_WAIT ani pri ceste do depa sa obchod nespustí.
            if (!vd.tradeDoneAtStation
                && (STATION_WAIT - vd.waitTimer) >= TRADE_DELAY)
            {
                vd.tradeDoneAtStation = true;
                TryExecuteTradeAtStation(vd);
            }

            if (vd.waitTimer <= 0f)
            {
                vd.isWaiting = false;
                vd.waitTimer = 0f;

                if (vd.isReturningToDepot)
                    DispatchToDepot(vd);
                else if (vd.isGoingToDepotViaStation)
                    DispatchToDepot(vd);
                else
                    AdvanceStation(vd);
            }
            return;
        }

        // ── Čakáme na výpočet novej trasy
        if (vd.activePath == null)
        {
            if (vd.isComputingPath) return;
            OnPathComplete(vd);
            return;
        }

        // ── Pohyb vozidla pozdĺž trasy
        vd.vehicleDistance += vd.vehicleSpeed * Time.deltaTime;

        UpdateCurrentTile(vd);

        Vector3 headPos = vd.activePath.GetPositionAtDistance(vd.vehicleDistance);
        vd.vehicleObject.transform.position = headPos;

        // ── Vizuálne vozidlo (1 kváder)
        UpdateVehicleVisual(vd);

        // ── Detekcia konca trasy
        if (vd.vehicleDistance >= vd.activePath.totalLength)
        {
            vd.vehicleDistance = vd.activePath.totalLength;
            vd.currentTile = vd.currentPath != null && vd.currentPath.Count > 0
                ? vd.currentPath[vd.currentPath.Count - 1]
                : vd.currentTile;

            if (vd.pendingPath != null)
            {
                List<Vector2Int> pending = vd.pendingPath;
                vd.pendingPath = null;
                ApplyNewPath(vd, pending);
                return;
            }

            vd.activePath = null;
            OnPathComplete(vd);
        }
    }

    void UpdateCurrentTile(VehicleData vd)
    {
        if (vd.currentPath == null || vd.activePath == null) return;
        if (vd.waypointToTileIdx == null || vd.waypointToTileIdx.Length == 0) return;

        float[] cumLens = vd.activePath.cumulativeLengths;
        int lastPassedIdx = 0;
        for (int i = 0; i < cumLens.Length; i++)
        {
            if (cumLens[i] <= vd.vehicleDistance)
                lastPassedIdx = i;
            else
                break;
        }

        int subPathTileIdx = vd.waypointToTileIdx[lastPassedIdx];
        int tileIdx = vd.pathIndexAtPathStart + subPathTileIdx;
        if (tileIdx >= 0 && tileIdx < vd.currentPath.Count)
            vd.currentTile = vd.currentPath[tileIdx];
    }

    // =====================================================================
    // VIZUÁLNE VOZIDLO – 1 KVÁDER (žiadne vagóny)
    // =====================================================================

    /// <summary>
    /// Aktualizuje pozíciu, rotáciu a viditeľnosť vozidla.
    /// Oproti TrainSystem UpdateConsistVisuals (kde sa iteruje cez
    /// lokomotívu + N vagónov) ide o jediný kváder bez offsetu.
    /// </summary>
    void UpdateVehicleVisual(VehicleData vd)
    {
        if (vd.activePath == null) return;
        if (vd.vehicleBody == null) return;

        const float DEPOT_HIDE_RADIUS = 0.55f;
        bool isReturning = vd.isReturningToDepot;
        Vector3 depotCenter = TileCenter(new Vector2Int(vd.depotX, vd.depotZ));

        bool shouldBeVisible = true;

        // ── Skrývanie pri návrate do depa
        if (isReturning)
        {
            Vector3 pos = vd.activePath.GetPositionAtDistance(vd.vehicleDistance);
            if (Vector3.Distance(pos, depotCenter) < DEPOT_HIDE_RADIUS)
                shouldBeVisible = false;
        }

        if (vd.vehicleBody.activeSelf != shouldBeVisible)
            vd.vehicleBody.SetActive(shouldBeVisible);

        if (!shouldBeVisible) return;

        Vector3 posBody = vd.activePath.GetPositionAtDistance(vd.vehicleDistance);
        if (float.IsNaN(posBody.x) || float.IsNaN(posBody.y) || float.IsNaN(posBody.z))
        {
            Debug.LogWarning($"[VehicleSystem] NaN pozícia – preskočené.");
            return;
        }

        Vector3 dir = vd.activePath.GetDirectionAtDistance(vd.vehicleDistance);
        Quaternion rot = Quaternion.LookRotation(dir, Vector3.up);

        vd.vehicleBody.transform.position = posBody;
        vd.vehicleBody.transform.rotation = rot;
    }

    // =====================================================================
    // DOKONČENIE CESTY
    // =====================================================================

    void OnPathComplete(VehicleData vd)
    {
        if (vd.isReturningToDepot)
        {
            vd.isRunning = false;
            vd.isAtDepot = true;
            vd.isReturningToDepot = false;
            vd.isGoingToDepotViaStation = false;
            vd.currentTile = new Vector2Int(vd.depotX, vd.depotZ);
            vd.vehicleObject.transform.position = TileCenter(vd.currentTile);
            vd.activePath = null;
            vd.vehicleDistance = 0f;

            HideVehicle(vd);
            Debug.Log($"[VehicleSystem] Vozidlo z depa [{vd.depotX},{vd.depotZ}] dorazilo do DEPA – skryté.");
            return;
        }

        if (vd.isGoingToDepotViaStation)
        {
            bool onStation = vd.stations.Contains(vd.currentTile)
                             && GetRoadTileID(vd.currentTile.x, vd.currentTile.y) == 2;
            if (onStation)
            {
                Debug.Log($"[VehicleSystem] Vozidlo dorazilo na stanicu [{vd.currentTile.x},{vd.currentTile.y}] pred depom – čaká {STATION_WAIT}s.");
                vd.vehicleObject.transform.position = TileCenter(vd.currentTile);
                vd.isWaiting = true;
                vd.waitTimer = STATION_WAIT;
                // Zastávka pred návratom do depa – obchod sa nerealizuje.
                vd.tradeDoneAtStation = true;
            }
            else
            {
                Debug.LogWarning($"[VehicleSystem] Vozidlo nedosiahlo stanicu pred depom. Zastane.");
                vd.isRunning = false;
                vd.isGoingToDepotViaStation = false;
            }
            return;
        }

        Vector2Int targetStation = vd.stations[vd.currentStationIndex];
        if (vd.currentTile == targetStation)
        {
            if (vd.pendingReturnToDepot)
            {
                vd.pendingReturnToDepot = false;
                vd.isGoingToDepotViaStation = true;
                Debug.Log($"[VehicleSystem] Vozidlo dorazilo na stanicu [{targetStation.x},{targetStation.y}] (pending návrat) – čaká {STATION_WAIT}s, potom depo.");
                // Posledná zastávka pred depom – obchod sa nerealizuje.
                vd.tradeDoneAtStation = true;
            }
            else
            {
                Debug.Log($"[VehicleSystem] Vozidlo dorazilo na stanicu [{targetStation.x},{targetStation.y}] – čaká {STATION_WAIT}s.");
                // Riadna zastávka na cieľovej stanici – po 2 s prebehne obchod.
                vd.tradeDoneAtStation = false;
            }
            vd.vehicleObject.transform.position = TileCenter(vd.currentTile);
            vd.isWaiting = true;
            vd.waitTimer = STATION_WAIT;
        }
        else
        {
            Debug.LogWarning($"[VehicleSystem] Vozidlo nedosiahlo stanicu [{targetStation.x},{targetStation.y}]. Čaká {BREAK_WAIT}s.");
            vd.isWaiting = true;
            vd.waitTimer = BREAK_WAIT;
            // Núdzové prerušenie – nie je to zastávka na stanici, žiadny obchod.
            vd.tradeDoneAtStation = true;
        }
    }

    void HideVehicle(VehicleData vd)
    {
        if (vd.vehicleBody != null) vd.vehicleBody.SetActive(false);
    }

    // =====================================================================
    // OBCHOD NA STANICI – TRADE (VehicleTradeSystem)
    // =====================================================================

    /// <summary>
    /// Vykoná výmenu tovaru medzi vozidlom a továrňami stanice, na ktorej
    /// vozidlo práve čaká. Volá sa z UpdateVehicle po 2 s čakania
    /// (TRADE_DELAY), práve raz za zastávku.
    ///
    /// Postup (analógia k TrainSystem.TryExecuteTradeAtStation):
    ///   1) Zisti tile, na ktorom vozidlo stojí (cieľová stanica).
    ///   2) Nájdi StationInstance pre tento tile v RoadStationRegistry.
    ///      Ak ešte nie je zaregistrovaná (napr. stanica pribudla bez
    ///      následného scanu), dorovná sa cez RescanAll.
    ///   3) Odovzdaj vozidlo + stanicu do VehicleTradeSystem.Execute.
    ///   4) Podľa výsledku vypíš Debug.Log (úspech / neúspech).
    ///
    /// Výpisy presne zodpovedajú zadaniu:
    ///   úspech  → "Predaj prebehol úspešne, cena {N} euro"
    ///   neúspech→ "Obchod neprebehol."
    /// </summary>
    void TryExecuteTradeAtStation(VehicleData vd)
    {
        // Bezpečnostné kontroly – bez dátovej štruktúry vozidla nemá
        // obchod zmysel.
        if (vd == null || vd.vehicleInstance == null)
        {
            Debug.Log("Obchod neprebehol.");
            return;
        }

        Vector2Int stationTile = vd.currentTile;

        // Nájdi cestnú stanicu v registri. Ak chýba, skús dorovnať register.
        StationInstance station = RoadStationRegistry.GetStationAt(stationTile.x, stationTile.y);
        if (station == null)
        {
            RoadStationRegistry.RescanAll();
            station = RoadStationRegistry.GetStationAt(stationTile.x, stationTile.y);
        }

        if (station == null)
        {
            Debug.Log("Obchod neprebehol.");
            Debug.LogWarning($"[VehicleSystem] Pre tile [{stationTile.x},{stationTile.y}] " +
                             $"sa nenašla cestná StationInstance – obchod sa neuskutočnil.");
            return;
        }

        // Vykonaj samotnú transakciu (čisto dátová operácia).
        VehicleTradeSystem.TradeResult result =
            VehicleTradeSystem.Execute(vd.vehicleInstance, station);

        if (result.Success)
        {
            // Pripíš zárobok na herné konto – preprava tovaru je jediný zdroj
            // príjmu, ktorý drží konto v pluse (a umožní reset cenového
            // násobiteľa po prepadnutí do mínusu).
            if (result.Revenue > 0)
            {
                if (GameEconomy.instance != null)
                    GameEconomy.instance.AddCredits((uint)result.Revenue);

                // EVIDENCIA pre ročnú uzávierku – tržba z úspešnej prepravy
                // (jediný zdroj príjmu hry, rovnako pre vlaky aj vozidlá).
                BudgetSystem.instance?.RecordRevenue(result.Revenue);
            }

            Debug.Log($"Obchodná transakcia bola vykonaná úspešne, suma {result.Revenue} CR.");
            Debug.Log($"[VehicleSystem] Obchod na stanici [{stationTile.x},{stationTile.y}]: " +
                      $"naložené {result.LoadedUnits}, vyložené {result.UnloadedUnits}, " +
                      $"zárobok {result.Revenue} CR.");

            // Plávajúci cenový label nad vozidlom na 3 s. Volá sa presne tu –
            // teda po 2 s od príchodu na stanicu (keď prebehne transakcia).
            if (vd.vehicleBody != null)
                SalePriceLabelManager.Instance.ShowPrice(
                    vd.vehicleBody.transform.position, result.Revenue);
        }
        else
        {
            Debug.Log("Obchod neprebehol.");
            Debug.Log($"[VehicleSystem] Obchod na stanici [{stationTile.x},{stationTile.y}] " +
                      $"neprebehol – dôvod: {result.Reason}.");
        }
    }

    // =====================================================================
    // ADVANCE STATION – OBRAT SMERU
    // =====================================================================

    void AdvanceStation(VehicleData vd)
    {
        if (!vd.reverseDirection)
        {
            vd.currentStationIndex++;
            if (vd.currentStationIndex >= vd.stations.Count)
            {
                vd.reverseDirection = true;
                vd.currentStationIndex = vd.stations.Count - 2;
                if (vd.currentStationIndex < 0) vd.currentStationIndex = 0;
            }
        }
        else
        {
            vd.currentStationIndex--;
            if (vd.currentStationIndex < 0)
            {
                vd.reverseDirection = false;
                vd.currentStationIndex = 1;
                if (vd.currentStationIndex >= vd.stations.Count)
                    vd.currentStationIndex = 0;
            }
        }

        ComputeNextPathAsync(vd);
    }

    // =====================================================================
    // ASYNC A* WRAPPER
    // =====================================================================

    void ComputeNextPathAsync(VehicleData vd)
    {
        if (vd.stations.Count == 0) return;
        if (vd.isComputingPath) return;

        Vector2Int goal = vd.stations[vd.currentStationIndex];
        Vector2Int start = vd.currentTile;
        int key = DepotKey(vd.depotX, vd.depotZ);
        var snap = SnapshotTileGrid();

        vd.isComputingPath = true;

        Task.Run(() =>
        {
            var path = AStarPathThreaded(snap, start, goal);
            _pendingPathResults.Enqueue(new PathResult
            {
                depotKey = key,
                path = path,
                isReturnToDepot = false
            });
        });
    }

    // =====================================================================
    // TRIGGER MAP CHANGED REROUTE
    // =====================================================================

    void TriggerMapChangedReroute()
    {
        // Mapa sa zmenila (pribudli/ubudli cesty, stanice alebo továrne) –
        // prepočítaj 9×9 zóny cestných staníc a ich evidenciu tovární.
        // Lacná operácia (analógia k TrainSystem.TriggerMapChangedReroute,
        // ktorý volá StationRegistry.RescanAll()).
        RoadStationRegistry.RescanAll();

        var snap = SnapshotTileGrid();

        foreach (var kvp in vehicles)
        {
            VehicleData vd = kvp.Value;
            if (!vd.isRunning) continue;
            if (vd.isComputingPath) continue;

            int key = kvp.Key;

            if (vd.isReturningToDepot)
            {
                Vector2Int start = vd.currentTile;
                Vector2Int goal = new Vector2Int(vd.depotX, vd.depotZ);
                vd.isComputingPath = true;
                Task.Run(() =>
                {
                    var path = AStarPathThreaded(snap, start, goal);
                    _pendingPathResults.Enqueue(new PathResult
                    {
                        depotKey = key,
                        path = path,
                        isReturnToDepot = true,
                        destination = PathDestination.Depot
                    });
                });
            }
            else if (vd.isGoingToDepotViaStation)
            {
                Vector2Int? nearest = FindNearestStation(vd);
                if (!nearest.HasValue) continue;
                Vector2Int start = vd.currentTile;
                Vector2Int goal = nearest.Value;
                vd.isComputingPath = true;
                Task.Run(() =>
                {
                    var path = AStarPathThreaded(snap, start, goal);
                    _pendingPathResults.Enqueue(new PathResult
                    {
                        depotKey = key,
                        path = path,
                        isReturnToDepot = false,
                        destination = PathDestination.ReturnViaStation
                    });
                });
            }
            else
            {
                if (vd.stations.Count == 0) continue;
                Vector2Int start = vd.currentTile;
                Vector2Int goal = vd.stations[vd.currentStationIndex];
                vd.isComputingPath = true;
                Task.Run(() =>
                {
                    var path = AStarPathThreaded(snap, start, goal);
                    _pendingPathResults.Enqueue(new PathResult
                    {
                        depotKey = key,
                        path = path,
                        isReturnToDepot = false,
                        destination = PathDestination.Station
                    });
                });
            }
        }
    }

    Vector2Int? FindNearestStation(VehicleData vd)
    {
        Vector2Int? nearest = null;
        int bestDist = int.MaxValue;
        foreach (var st in vd.stations)
        {
            if (GetRoadTileID(st.x, st.y) != 2) continue;
            int dist = Math.Abs(vd.currentTile.x - st.x) + Math.Abs(vd.currentTile.y - st.y);
            if (dist < bestDist) { bestDist = dist; nearest = st; }
        }
        return nearest;
    }

    // =====================================================================
    // APLIKÁCIA VÝSLEDKOV A*
    // =====================================================================

    void ApplyPendingPathResults()
    {
        while (_pendingPathResults.TryDequeue(out PathResult result))
        {
            if (!vehicles.TryGetValue(result.depotKey, out VehicleData vd)) continue;

            vd.isComputingPath = false;
            bool hasPath = result.path != null && result.path.Count > 0;

            if (result.destination == PathDestination.Depot)
            {
                if (hasPath) { ApplyNewPath(vd, result.path); vd.isWaiting = false; }
                else
                {
                    vd.isRunning = false; vd.isWaiting = true; vd.waitTimer = BREAK_WAIT;
                    Debug.LogWarning($"[VehicleSystem] Vozidlo z depa [{vd.depotX},{vd.depotZ}] nemôže nájsť cestu do depa, čaká.");
                }
                continue;
            }

            if (result.destination == PathDestination.ReturnViaStation)
            {
                if (hasPath) { ApplyNewPath(vd, result.path); vd.isWaiting = false; }
                else
                {
                    Debug.LogWarning($"[VehicleSystem] Vozidlo z depa [{vd.depotX},{vd.depotZ}] nemôže nájsť cestu na stanicu. Zastane.");
                    vd.isRunning = false; vd.isWaiting = false; vd.isGoingToDepotViaStation = false;
                }
                continue;
            }

            if (result.isReturnToDepot)
            {
                if (hasPath) { ApplyNewPath(vd, result.path); vd.isWaiting = false; }
                else
                {
                    vd.isRunning = false; vd.isWaiting = true; vd.waitTimer = BREAK_WAIT;
                    Debug.LogWarning($"[VehicleSystem] Vozidlo nemôže nájsť cestu do depa, čaká.");
                }
            }
            else
            {
                if (hasPath) { ApplyNewPath(vd, result.path); vd.isWaiting = false; }
                else
                {
                    bool anyStationExists = false;
                    foreach (var st in vd.stations)
                        if (GetRoadTileID(st.x, st.y) == 2) { anyStationExists = true; break; }

                    if (!anyStationExists)
                    {
                        Debug.Log($"[VehicleSystem] Vozidlo z depa [{vd.depotX},{vd.depotZ}]: žiadne dostupné stanice – vozidlo zastane. Použite Send To Depot pre návrat.");
                        vd.isRunning = false; vd.isWaiting = false;
                        vd.isStoppedAwaitingDepotReturn = true;
                        vd.currentPath = new List<Vector2Int>(); vd.pathIndex = 0;
                        vd.activePath = null;

                        if (vd.currentTile.x == vd.depotX && vd.currentTile.y == vd.depotZ)
                            vd.isAtDepot = true;
                    }
                    else
                    {
                        if (vd.stations.Count > 0)
                        {
                            var goal = vd.stations[vd.currentStationIndex];
                            Debug.LogWarning($"[VehicleSystem] A* nenašiel cestu do [{goal.x},{goal.y}]. Čakám {BREAK_WAIT}s.");
                        }
                        vd.isWaiting = true; vd.waitTimer = BREAK_WAIT;
                        vd.currentPath = new List<Vector2Int>(); vd.pathIndex = 0;
                        vd.activePath = null;

                        if (vd.currentTile.x == vd.depotX && vd.currentTile.y == vd.depotZ)
                            vd.isAtDepot = true;
                    }
                }
            }
        }
    }

    // =====================================================================
    // APPLY NEW PATH – konvertuje List<Vector2Int> na PathData (waypoint-based)
    // =====================================================================

    void ApplyNewPath(VehicleData vd, List<Vector2Int> newPath)
    {
        bool isMoving = vd.activePath != null && vd.vehicleDistance < vd.activePath.totalLength;

        Vector2Int startTile = vd.currentTile;
        int startIdxInNew = -1;
        for (int i = 0; i < newPath.Count; i++)
        {
            if (newPath[i] == startTile) { startIdxInNew = i; break; }
        }

        if (startIdxInNew < 0)
        {
            if (isMoving)
            {
                vd.pendingPath = newPath;
                return;
            }
            startIdxInNew = 0;
        }

        List<Vector2Int> subPath = newPath.GetRange(startIdxInNew, newPath.Count - startIdxInNew);

        vd.currentPath = newPath;
        vd.pathIndex = startIdxInNew;
        vd.pathIndexAtPathStart = startIdxInNew;
        vd.moveTimer = 0f;

        BuildWaypointPath(subPath, out var pts, out var tileIdxList);
        var newPathData = new PathData(pts);

        float expectedTime = (subPath.Count - 1) * MOVE_TIME;
        vd.vehicleSpeed = (newPathData.totalLength > 0f && expectedTime > 0f)
            ? newPathData.totalLength / expectedTime
            : 1f / MOVE_TIME;

        vd.vehicleDistance = 0f;
        vd.activePath = newPathData;
        vd.waypointToTileIdx = tileIdxList.ToArray();
    }

    void RebuildActivePathFromCurrentPath(VehicleData vd)
    {
        if (vd.currentPath == null || vd.currentPath.Count == 0)
        {
            vd.activePath = null;
            return;
        }

        int startIdx = vd.pathIndex;
        if (startIdx >= vd.currentPath.Count) { vd.activePath = null; return; }

        List<Vector2Int> sub = vd.currentPath.GetRange(startIdx, vd.currentPath.Count - startIdx);

        BuildWaypointPath(sub, out var pts, out var tileIdxList);
        var pd = new PathData(pts);

        vd.pathIndexAtPathStart = startIdx;
        float expectedTime = (sub.Count - 1) * MOVE_TIME;
        vd.vehicleSpeed = (pd.totalLength > 0f && expectedTime > 0f)
            ? pd.totalLength / expectedTime
            : 1f / MOVE_TIME;
        vd.vehicleDistance = 0f;
        vd.activePath = pd;
        vd.waypointToTileIdx = tileIdxList.ToArray();
    }

    // =====================================================================
    // A* PATHFINDING – THREAD-SAFE
    // =====================================================================

    class AStarNode
    {
        public Vector2Int pos;
        public AStarNode parent;
        public float g, h;
        public float f => g + h;

        public AStarNode(Vector2Int p, AStarNode par, float g, float h)
        {
            pos = p; parent = par; this.g = g; this.h = h;
        }
    }

    static readonly Vector2Int[] DIRECTIONS =
    {
        new Vector2Int( 1, 0),
        new Vector2Int(-1, 0),
        new Vector2Int( 0, 1),
        new Vector2Int( 0,-1)
    };

    static List<Vector2Int> AStarPathThreaded(TileGridSnapshot snap, Vector2Int start, Vector2Int goal)
    {
        if (start == goal) return new List<Vector2Int>();

        var open = new List<AStarNode>();
        var closed = new HashSet<Vector2Int>();
        var nodeMap = new Dictionary<Vector2Int, AStarNode>();

        open.Add(new AStarNode(start, null, 0f, Heuristic(start, goal)));
        nodeMap[start] = open[0];

        int iterations = 0;
        const int MAX_ITER = 10000;

        while (open.Count > 0 && iterations < MAX_ITER)
        {
            iterations++;
            int bestIdx = 0;
            for (int i = 1; i < open.Count; i++)
                if (open[i].f < open[bestIdx].f) bestIdx = i;

            AStarNode current = open[bestIdx];
            open.RemoveAt(bestIdx);

            if (current.pos == goal)
                return ReconstructPath(current);

            closed.Add(current.pos);

            foreach (var dir in DIRECTIONS)
            {
                Vector2Int neighbor = current.pos + dir;
                if (closed.Contains(neighbor)) continue;

                IndicatrixAPI.DirectionMask dirMask = DirFromStep(dir);
                if (!CanMove(snap.tileIDs, snap.connections, current.pos, neighbor, dirMask))
                    continue;

                float newG = current.g + 1f;
                if (nodeMap.TryGetValue(neighbor, out AStarNode existing))
                {
                    if (newG < existing.g) { existing.g = newG; existing.parent = current; }
                }
                else
                {
                    var node = new AStarNode(neighbor, current, newG, Heuristic(neighbor, goal));
                    open.Add(node);
                    nodeMap[neighbor] = node;
                }
            }
        }

        return null;
    }

    static float Heuristic(Vector2Int a, Vector2Int b)
        => Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);

    static List<Vector2Int> ReconstructPath(AStarNode node)
    {
        var path = new List<Vector2Int>();
        var cur = node;
        while (cur.parent != null) { path.Add(cur.pos); cur = cur.parent; }
        path.Reverse();
        return path;
    }

    public void WriteSave(System.IO.BinaryWriter bw)
    {
        bw.Write(vehicles.Count);
        foreach (var kvp in vehicles)
        {
            VehicleData vd = kvp.Value;
            var inst = vd.vehicleInstance;

            bw.Write(vd.depotX);
            bw.Write(vd.depotZ);

            // Typ vozidla ukladáme ako Name (CreateVehicle berie názov typu).
            bw.Write(inst != null ? inst.Spec.Name : "");

            bw.Write(inst != null ? inst.Age : 0);
            bw.Write(inst != null ? inst.CurrentCapacity : 0);

            bw.Write(vd.stations != null ? vd.stations.Count : 0);
            if (vd.stations != null)
                foreach (var s in vd.stations) { bw.Write(s.x); bw.Write(s.y); }

            bw.Write(vd.isRunning);
        }
    }

    public void ReadSave(System.IO.BinaryReader br)
    {
        int count = br.ReadInt32();
        for (int k = 0; k < count; k++)
        {
            int dx = br.ReadInt32();
            int dz = br.ReadInt32();
            string typeName = br.ReadString();
            int age = br.ReadInt32();
            int cargo = br.ReadInt32();

            int stationCnt = br.ReadInt32();
            var stations = new System.Collections.Generic.List<Vector2Int>(stationCnt);
            for (int i = 0; i < stationCnt; i++)
                stations.Add(new Vector2Int(br.ReadInt32(), br.ReadInt32()));

            bool wasRunning = br.ReadBoolean();

            if (!CreateVehicle(dx, dz, typeName))
                continue;

            VehicleData vd = GetVehicle(dx, dz);
            if (vd == null) continue;

            if (vd.vehicleInstance != null)
            {
                vd.vehicleInstance.Age = age;
                vd.vehicleInstance.CurrentCapacity = cargo;
            }

            foreach (var s in stations)
                AddStation(dx, dz, s.x, s.y);

            if (wasRunning)
                StartVehicle(dx, dz);
        }
    }

}

VehicleTradeSystem.cs

using UnityEngine;
using Game.VehicleStock;

/// <summary>
/// VehicleTradeSystem.cs
/// ------------------------------------------------------------------
/// TRANSAKČNÝ ALGORITMUS PRE CESTNÉ VOZIDLÁ – výmena tovaru medzi
/// vozidlom a továrňami stanice.
///
/// Toto je CESTNÝ EKVIVALENT triedy TradeSystem (ktorá rieši obchod pre
/// VLAKY). Princíp obchodu je medzi vlakmi a vozidlami ROVNAKÝ – kontroluje
/// sa len surovina a množstvo – líši sa iba dátová štruktúra "dopravcu":
///
///     VLAK    = TrainInstance + List&lt;WagonInstance&gt;  (náklad nesú vagóny)
///     VOZIDLO = VehicleInstance                       (JEDEN kus, žiadne vagóny)
///
/// Vozidlo sa správa "akoby jeden vagón": má MaximumCapacity a premenlivý
/// CurrentCapacity (VehicleStock.cs). Preto je cestná transakcia jednoduchšia
/// – tam, kde TradeSystem prechádza vagón po vagóne (FillWagons / UnloadWagons),
/// tu pracujeme s jediným kusom (FillVehicle / UnloadVehicle).
///
/// ------------------------------------------------------------------
/// AKO TO FUNGUJE – KONCEPT (zhodný s TradeSystem)
///
/// Vozidlo má typ (VehicleType), ktorý nesie ResourceType cez ČÍSELNÉ ID:
///     VehicleType.Id == (int)ResourceType
///     ("Coal Truck" #1 → ResourceType.Coal,  "Wood Truck" #2 → Wood)
/// Mapovanie je 1:1 – presne ako WagonType.Id v TradeSystem.
///
/// Stanica (StationInstance, evidovaná v RoadStationRegistry) eviduje
/// 0..N tovární vo svojej 9×9 zóne. Každá továreň má pre danú surovinu buď
/// VÝDAJOVÝ slot (UnLoad – surovinu produkuje) alebo PRÍJMOVÝ slot
/// (Load – surovinu spotrebúva).
///
/// Keď vozidlo zastane na stanici, pre svoj typ suroviny urobí:
///
///   KROK 1 – VYLOŽENIE (Unload):
///     Ak je v zóne stanice továreň, ktorá danú surovinu PRIJÍMA (Load slot),
///     a vo vozidle niečo je → vyloží náklad do tej továrne.
///
///   KROK 2 – NALOŽENIE (Load):
///     Ak je v zóne stanice továreň, ktorá danú surovinu VYDÁVA (UnLoad slot),
///     a vo vozidle je voľné miesto → naloží z tej továrne do vozidla.
///
/// Obchod je ÚSPEŠNÝ ("Predaj prebehol") IBA vtedy, keď sa pri zastávke
/// SKUTOČNE VYLOŽILA aspoň 1 jednotka tovaru do príjmovej továrne.
/// Naloženie surovín do vozidla NEROBÍ obchod úspešným – je to len presun,
/// príprava na predaj na ďalšej stanici (rovnaké pravidlo ako pri vlakoch).
/// ------------------------------------------------------------------
/// </summary>
public static class VehicleTradeSystem
{
    // Cena predaja sa už NEráta paušálne za jednotku. Kompletný cenník (cena
    // ZA PLNÉ VOZIDLO podľa suroviny + alikvotná časť pri neúplnom vozidle +
    // dynamický násobiteľ pri zápornom konte) je centralizovaný v
    // ResourcePricing.RoadSaleRevenue(...). Pozri krok 4 v Execute(...).

    // ==================================================================
    // VÝSLEDOK TRANSAKCIE
    // ==================================================================

    /// <summary>
    /// Výsledok jednej zastávky vozidla na stanici. VehicleSystem si z neho
    /// prečíta Success + Revenue a vypíše príslušný Debug.Log.
    /// (Štruktúra zhodná s TradeSystem.TradeResult.)
    /// </summary>
    public struct TradeResult
    {
        /// <summary>True, ak sa vyložila (predala) aspoň 1 jednotka tovaru.</summary>
        public bool Success;

        /// <summary>Koľko jednotiek sa naložilo z továrne do vozidla.</summary>
        public int LoadedUnits;

        /// <summary>Koľko jednotiek sa vyložilo z vozidla do továrne.</summary>
        public int UnloadedUnits;

        /// <summary>Celkový výnos z obchodu (euro).</summary>
        public int Revenue;

        /// <summary>Stručný textový dôvod (najmä pri neúspechu) – na debug.</summary>
        public string Reason;

        /// <summary>Celkový počet presunutých jednotiek (load + unload).</summary>
        public int MovedUnits => LoadedUnits + UnloadedUnits;
    }

    // ==================================================================
    // HLAVNÝ VSTUP – vykonaj transakciu pre vozidlo na stanici
    // ==================================================================

    /// <summary>
    /// Vykoná výmenu tovaru medzi vozidlom <paramref name="vehicle"/> a
    /// továrňami v 9×9 zóne stanice <paramref name="station"/>.
    ///
    /// Postup (analogický k TradeSystem.Execute):
    ///   0) Validácia (vozidlo existuje, stanica eviduje aspoň 1 továreň).
    ///   1) Zisti surovinu, s ktorou vozidlo pracuje (z typu vozidla).
    ///   2) UNLOAD – ak je v zóne príjmová továreň, vyloží náklad do nej.
    ///   3) LOAD   – ak je v zóne výdajová továreň, naloží z nej do vozidla.
    ///   4) Vyhodnoť úspech a vyčísli cenu.
    ///
    /// Metóda je čisto dátová (žiadne Unity GameObject API), takže sa dá
    /// volať z hlavného vlákna v Update slučke VehicleSystem-u bez rizika.
    /// </summary>
    public static TradeResult Execute(VehicleInstance vehicle, StationInstance station)
    {
        var result = new TradeResult
        {
            Success = false,
            LoadedUnits = 0,
            UnloadedUnits = 0,
            Revenue = 0,
            Reason = ""
        };

        // ---- 0) VALIDÁCIA -------------------------------------------------
        if (vehicle == null)
        {
            result.Reason = "vozidlo nemá dátovú štruktúru (VehicleInstance == null)";
            return result;
        }
        if (station == null)
        {
            result.Reason = "neznáma stanica";
            return result;
        }
        if (!station.HasFactories)
        {
            result.Reason = "stanica neeviduje žiadnu továreň v 9×9 zóne";
            return result;
        }

        // ---- 1) SUROVINA VOZIDLA -----------------------------------------
        ResourceType vehicleResource = VehicleResource(vehicle);
        if (vehicleResource == ResourceType.None)
        {
            result.Reason = $"typ vozidla '{vehicle.Type}' nemá priradenú surovinu";
            return result;
        }

        // ---- 2) UNLOAD (vyloženie do príjmovej továrne) -------------------
        // Vykoná sa len ak je v zóne stanice továreň, ktorá danú surovinu
        // PRIJÍMA. Pri čisto výdajovej stanici sa tento krok preskočí –
        // tým je ošetrený "bonus": vozidlo môže prejsť cez výdajovú stanicu
        // a náklad mu zostane.
        FactoryInstance consumer = station.FindConsumer(vehicleResource);
        if (consumer != null)
        {
            ResourceSlot loadSlot = consumer.GetLoadSlot(vehicleResource);
            result.UnloadedUnits = UnloadVehicle(vehicle, loadSlot);
            if (result.UnloadedUnits > 0)
                Debug.Log($"[VehicleTradeSystem] Vyložené {result.UnloadedUnits}× {vehicleResource} " +
                          $"do '{consumer.Name}' (slot {loadSlot}).");
        }

        // ---- 3) LOAD (naloženie z výdajovej továrne) ----------------------
        // Vykoná sa len ak je v zóne stanice továreň, ktorá danú surovinu
        // VYDÁVA. FillVehicle dopĺňa do VOĽNÉHO miesta, takže existujúci
        // náklad (z inej stanice) zostáva a nová surovina sa k nemu pripočíta.
        FactoryInstance supplier = station.FindSupplier(vehicleResource);
        if (supplier != null)
        {
            ResourceSlot unloadSlot = supplier.GetUnLoadSlot(vehicleResource);
            result.LoadedUnits = FillVehicle(vehicle, unloadSlot);
            if (result.LoadedUnits > 0)
                Debug.Log($"[VehicleTradeSystem] Naložené {result.LoadedUnits}× {vehicleResource} " +
                          $"z '{supplier.Name}' (slot {unloadSlot}).");
        }

        // ---- 4) VYHODNOTENIE ---------------------------------------------
        // DÔLEŽITÉ: peniaze plynú IBA z VYLOŽENIA (predaja tovaru továrni).
        // Naloženie surovín z výdajovej továrne do vozidla je len presun –
        // NEGENERUJE žiadny výnos a samo o sebe nerobí obchod úspešným.
        result.Success = result.UnloadedUnits > 0;

        if (result.Success)
        {
            // Cena ZA PLNÉ VOZIDLO podľa suroviny, alikvotne podľa reálne
            // predaného (vyloženého) množstva, prenásobená aktuálnym cenovým
            // násobiteľom konta. Menovateľom alikvotného prepočtu je
            // MaximumCapacity vozidla.
            result.Revenue = ResourcePricing.RoadSaleRevenue(
                vehicleResource, result.UnloadedUnits, vehicle.MaximumCapacity);
        }
        else
        {
            // Žiadny PREDAJ (vyloženie) – upresni dôvod pre debug.
            if (consumer == null)
            {
                if (result.LoadedUnits > 0)
                    result.Reason = $"naložené {result.LoadedUnits}× {vehicleResource}, " +
                                    $"ale stanica nemá príjmovú továreň – predaj sa nekoná";
                else if (supplier == null)
                    result.Reason = $"v zóne stanice nie je továreň pre surovinu {vehicleResource}";
                else
                    result.Reason = "stanica nemá príjmovú továreň – nie je kam predať";
            }
            else
            {
                result.Reason = "vozidlo prázdne alebo príjmová továreň plná – nič sa nevyložilo";
            }
        }

        return result;
    }

    // ==================================================================
    // POMOCNÉ – mapovanie typu vozidla na surovinu
    // ==================================================================

    /// <summary>
    /// Vráti surovinu, s ktorou vie dané vozidlo pracovať.
    ///
    /// Mapovanie je cez ČÍSELNÉ ID: VehicleType.Id == (int)ResourceType.
    /// Katalógy sú zámerne zladené (rovnako ako pri vlakoch):
    ///     "Coal Truck" má Id 1  →  ResourceType.Coal == 1
    ///     "Wood Truck" má Id 2  →  ResourceType.Wood == 2
    ///
    /// Ak by v budúcnosti ID nesedeli, stačí toto mapovanie nahradiť
    /// explicitnou tabuľkou (Dictionary&lt;int, ResourceType&gt;).
    /// </summary>
    public static ResourceType VehicleResource(VehicleInstance vehicle)
    {
        if (vehicle == null) return ResourceType.None;

        int id = vehicle.Type.Id;
        if (System.Enum.IsDefined(typeof(ResourceType), id))
            return (ResourceType)id;

        return ResourceType.None;
    }

    // ==================================================================
    // POMOCNÉ – LOAD: naplnenie vozidla z výdajového slotu továrne
    // ==================================================================

    /// <summary>
    /// Naplnenie vozidla surovinou zo skladu továrne
    /// (<paramref name="source"/> = UnLoad slot).
    ///
    /// ALGORITMUS:
    ///   Vozidlo je JEDEN kus – naplní sa do svojej maximálnej kapacity,
    ///   pokým je v továrni surovina k dispozícii. Naloží sa
    ///   min(voľné miesto vo vozidle, dostupné v továrni).
    ///
    ///   (Toto je zjednodušená verzia TradeSystem.FillWagons, ktorý
    ///   prechádza vagón po vagóne – vozidlo má len jeden "vagón".)
    ///
    /// Rešpektuje BONUS: ak vo vozidle už nejaká surovina je (z inej
    /// stanice), dopĺňa sa len jeho VOĽNÉ miesto – existujúce množstvo
    /// sa nemaže.
    ///
    /// Vráti, koľko jednotiek sa SKUTOČNE naložilo (a odpočítalo z továrne).
    /// </summary>
    public static int FillVehicle(VehicleInstance vehicle, ResourceSlot source)
    {
        if (vehicle == null || source == null) return 0;
        if (source.IsEmpty) return 0;

        // Voľné miesto vo vozidle.
        int free = vehicle.MaximumCapacity - vehicle.CurrentCapacity;
        if (free <= 0) return 0;            // vozidlo plné

        // Do vozidla ide min(voľné miesto, dostupné v továrni).
        int toLoad = Mathf.Min(free, source.amount);
        if (toLoad <= 0) return 0;

        vehicle.CurrentCapacity += toLoad;

        // Odpočítaj naloženú surovinu zo skladu továrne.
        // ResourceSlot.Remove je orezané dostupným množstvom (bezpečné).
        source.Remove(toLoad);

        return toLoad;
    }

    // ==================================================================
    // POMOCNÉ – UNLOAD: vyloženie vozidla do príjmového slotu továrne
    // ==================================================================

    /// <summary>
    /// Vyloženie nákladu vozidla do skladu továrne
    /// (<paramref name="target"/> = Load slot).
    ///
    /// ALGORITMUS:
    ///   Náklad vozidla sa presunie do príjmového slotu továrne. Slot má
    ///   kapacitu – ak by sa nezmestilo všetko, presunie sa len toľko,
    ///   koľko je v slote voľné, a zvyšok ZOSTANE vo vozidle (odvezie ho ďalej).
    ///
    ///   (Toto je zjednodušená verzia TradeSystem.UnloadWagons – vozidlo
    ///   má len jeden "vagón".)
    ///
    /// Vráti, koľko jednotiek sa SKUTOČNE vyložilo (a pripočítalo k továrni).
    /// </summary>
    public static int UnloadVehicle(VehicleInstance vehicle, ResourceSlot target)
    {
        if (vehicle == null || target == null) return 0;
        if (vehicle.CurrentCapacity <= 0) return 0;     // prázdne vozidlo

        // Voľné miesto v príjmovom slote továrne.
        int free = target.capacity - target.amount;
        if (free <= 0) return 0;                        // továreň plná

        // Z vozidla presunieme min(jeho náklad, voľné miesto v továrni).
        int toUnload = Mathf.Min(vehicle.CurrentCapacity, free);
        if (toUnload <= 0) return 0;

        vehicle.CurrentCapacity -= toUnload;

        // Pripočítaj vyloženú surovinu do skladu továrne.
        // ResourceSlot.Add je orezané kapacitou (bezpečné).
        target.Add(toUnload);

        return toUnload;
    }
}