 ----------------------------------------------------
-- FahrgassenAutomatik FS25 – FINAL FIX
----------------------------------------------------
  fahrgassenAutomatik = {}
  fahrgassenAutomatik.modDir = g_currentModDirectory
  local specName = "spec_fahrgassenAutomatik"
  -- FS25 skip RidgeMarker WorkAreas
  
  local skipWorkAreas = {
      ["processRidgeMarkerArea"] = true
  }
  local function FA_isWorkAreaValid(wa)
      if skipWorkAreas[wa.functionName] then return false end
      if wa.workAreaType == WorkAreaType.AUXILIARY then return false end
      return true
  end
  
  local function FA_l10n(key)
    if g_i18n and g_i18n:hasText(key) then
        return g_i18n:getText(key)
    end
    return key
end

-- =========================================
-- FA_SyncAreaEvent
-- =========================================
FA_SyncAreaEvent = {}
local FA_SyncAreaEvent_mt = Class(FA_SyncAreaEvent, Event)
InitEventClass(FA_SyncAreaEvent, "FA_SyncAreaEvent")
function FA_SyncAreaEvent:emptyNew() return Event.new(FA_SyncAreaEvent_mt) end
function FA_SyncAreaEvent:new(object, areaTotal, areaSession)
    local self = FA_SyncAreaEvent:emptyNew()
    self.object       = object
    self.areaTotal    = areaTotal
    self.areaSession  = areaSession
    return self
end
function FA_SyncAreaEvent:readStream(streamId, connection)
    self.object      = NetworkUtil.readNodeObject(streamId)
    self.areaTotal   = streamReadFloat32(streamId)
    self.areaSession = streamReadFloat32(streamId)
    self:run(connection)
end
function FA_SyncAreaEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteFloat32(streamId, self.areaTotal)
    streamWriteFloat32(streamId, self.areaSession)
end
function FA_SyncAreaEvent:run(connection)
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.areaTotal   = self.areaTotal
        spec.areaSession = self.areaSession
    end
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
end

FA_SetPassEvent = {}
local FA_SetPassEvent_mt = Class(FA_SetPassEvent, Event)
InitEventClass(FA_SetPassEvent, "FA_SetPassEvent")

function FA_SetPassEvent:emptyNew()
    return Event.new(FA_SetPassEvent_mt)
end

function FA_SetPassEvent:new(object, pass)
    local self = FA_SetPassEvent:emptyNew()
    self.object = object
    self.pass = pass
    return self
end

function FA_SetPassEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.pass = streamReadInt8(streamId)
    self.rmSide      = streamReadInt8(streamId)
    self.rmPassCount = streamReadInt32(streamId)
    self:run(connection)
end

function FA_SetPassEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteInt8(streamId, self.pass)
    -- RidgeMarker-Zustand mitschicken, damit alle Clients identisch sind
    local rmSide, rmCount = 0, 0
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        rmSide  = spec._rmForcedSide     or 0
        rmCount = spec._rmTotalPassCount or 0
    end
    streamWriteInt8(streamId,  rmSide)
    streamWriteInt32(streamId, rmCount)
end

function FA_SetPassEvent:run(connection)
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.pass = math.max(1, math.min(spec.passMax or 2, self.pass))
        fahrgassenAutomatik.setGassen(self.object, spec.pass == spec.gassenTarget)
        if connection:getIsServer() then
            -- Client empfaengt Server-Event: berechnete RidgeMarker-Seite uebernehmen
            if self.rmSide ~= nil then
                spec._rmTotalPassCount = self.rmPassCount
                spec._rmForcedSide     = self.rmSide
                if FA_RidgeMarker and FA_RidgeMarker.hasRidgeMarker(self.object) then
                    spec._rmModeChanged = true
                end
            end
        else
            -- Server empfaengt Client-Event: Pass-Wechsel verarbeiten
            fahrgassenAutomatik.onPassChanged(self.object)
        end
    end

    -- Server an alle anderen Clients weiterleiten
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
end
  fahrgassenAutomatik.INPUT_CONTEXT_NAME = "FA_INTERACTIVE_HUD"
  
  FA_SetGassenEvent = {}
local FA_SetGassenEvent_mt = Class(FA_SetGassenEvent, Event)
InitEventClass(FA_SetGassenEvent, "FA_SetGassenEvent")

function FA_SetGassenEvent:emptyNew()
    return Event.new(FA_SetGassenEvent_mt)
end

function FA_SetGassenEvent:new(object, state)
    local self = FA_SetGassenEvent:emptyNew()
    self.object = object
    self.state = state
    return self
end

function FA_SetGassenEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.state = streamReadBool(streamId)
    self:run(connection)
end

function FA_SetGassenEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteBool(streamId, self.state)
end

function FA_SetGassenEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end

    if self.object and self.object[specName] then
        local spec = self.object[specName]
        for _,wa in ipairs(spec.workAreas) do
            wa.disabled = not self.state
        end
        spec.gassenActive = self.state
    end
end

--------------------------------------------------------------------
-- FA_SetHalfSideEvent  (0=beide, 1=nurLinks, 2=nurRechts, 3=keine)
--------------------------------------------------------------------
FA_SetHalfSideEvent = {}
local FA_SetHalfSideEvent_mt = Class(FA_SetHalfSideEvent, Event)
InitEventClass(FA_SetHalfSideEvent, "FA_SetHalfSideEvent")

function FA_SetHalfSideEvent:emptyNew()
    return Event.new(FA_SetHalfSideEvent_mt)
end

function FA_SetHalfSideEvent:new(object, side)
    local self = FA_SetHalfSideEvent:emptyNew()
    self.object = object
    self.side   = side
    return self
end

function FA_SetHalfSideEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.side   = streamReadInt8(streamId)
    self:run(connection)
end

function FA_SetHalfSideEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteInt8(streamId, self.side)
end

function FA_SetHalfSideEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        self.object[specName].halfSide = self.side
        fahrgassenAutomatik.applyHalfSide(self.object)
    end
end

FA_SetModeEvent = {}
local FA_SetModeEvent_mt = Class(FA_SetModeEvent, Event)
InitEventClass(FA_SetModeEvent, "FA_SetModeEvent")

function FA_SetModeEvent:emptyNew()
    return Event.new(FA_SetModeEvent_mt)
end

function FA_SetModeEvent:new(object, mode)
    local self = FA_SetModeEvent:emptyNew()
    self.object = object
    self.mode = mode
    return self
end

function FA_SetModeEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.mode = streamReadString(streamId)
    self:run(connection)
end

function FA_SetModeEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteString(streamId, self.mode)
end

function FA_SetModeEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end

    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.mode = self.mode
    end
end

local function FA_hasEnhancedVehicle(self)
    local root = self.rootVehicle or self
    return root.vData ~= nil
        and root.vData.track ~= nil
        and root.vData.track.isCalculated == true
        and root.vData.opMode == 2
end

local function FA_getModeOrder(self)
    local modes = { "Pause", "Dauer", "GPS", "Manuel" }

    if FA_Courseplay.hasCourseplay(self) then
        table.insert(modes, 4, "Courseplay")
    end

    if FA_hasEnhancedVehicle(self) then
        table.insert(modes, 4, "EnhancedVehicle")
    end

    return modes
end

local function FA_getNextMode(self, currentMode)
    local modes = FA_getModeOrder(self)

    for i = 1, #modes do
        if modes[i] == currentMode then
            return modes[(i % #modes) + 1]
        end
    end

    return modes[1]
end

function FA_cpGetCurrentPassFromRef(self)
    local spec = self[specName]
    if spec == nil or spec.cpSegments == nil or #spec.cpSegments == 0 then
        return nil, nil
    end

    local root = self.rootVehicle or self
    -- ⭐ Implement-Position (Sämaschine) statt Traktor
    -- Traktor ragt am Bahnende ins Vorgewende → Segment wechselt zu früh
    local implementNode = self.components and self.components[1] and self.components[1].node
    local tractorNode   = root.components  and root.components[1]  and root.components[1].node
    local node = implementNode or tractorNode or self.rootNode
    if node == nil then
        return nil, nil
    end

    local x, _, z = getWorldTranslation(node)

    local bestSeg = nil
    local bestDist2 = math.huge

    local function pointToSegmentDist2(px, pz, ax, az, bx, bz)
        local abx = bx - ax
        local abz = bz - az
        local apx = px - ax
        local apz = pz - az

        local abLen2 = abx * abx + abz * abz
        if abLen2 < 0.000001 then
            local dx = px - ax
            local dz = pz - az
            return dx * dx + dz * dz
        end

        local t = (apx * abx + apz * abz) / abLen2
        if t < 0 then
            t = 0
        elseif t > 1 then
            t = 1
        end

        local cx = ax + t * abx
        local cz = az + t * abz

        local dx = px - cx
        local dz = pz - cz
        return dx * dx + dz * dz
    end

    for _, seg in ipairs(spec.cpSegments) do
        if seg.points and #seg.points > 0 and seg.pass ~= nil then
            local segBestDist2 = math.huge

            if #seg.points == 1 then
                local p = seg.points[1]
                local dx = x - p.x
                local dz = z - p.z
                segBestDist2 = dx * dx + dz * dz
            else
                for i = 1, #seg.points - 1 do
                    local a = seg.points[i]
                    local b = seg.points[i + 1]

                    local d2 = pointToSegmentDist2(x, z, a.x, a.z, b.x, b.z)
                    if d2 < segBestDist2 then
                        segBestDist2 = d2
                    end
                end
            end

            if segBestDist2 < bestDist2 then
                bestDist2 = segBestDist2
                bestSeg = seg
            end
        end
    end

    if bestSeg == nil then
        return nil, nil
    end

    if bestSeg.id ~= spec.lastDebugSeg then
        spec.lastDebugSeg = bestSeg.id
    end

    return bestSeg.pass, bestSeg
end

local function FA_isTramActive(spec, vehicle)
    if not spec then return false end

    -- Manuelle Fahrgasse aktiv (gilt in allen Modi)
    if spec.forcePass then return true end

    -- ⭐ Dauer → direkt vom Gerät lesen (deterministisch!)
    if spec.mode == "Dauer" then
        if vehicle and vehicle.getIsLowered then
            return vehicle:getIsLowered()
        end
        return false
    end

    -- ⭐ GPS + Manuel → über Pass
    if spec.mode == "GPS" or spec.mode == "Manuel" or spec.mode == "Courseplay" or spec.mode == "EnhancedVehicle" then
        return spec.pass == spec.gassenTarget
    end

    return false
end

-- =========================================
-- FA_ResetAreaEvent
-- =========================================
FA_ResetAreaEvent = {}
local FA_ResetAreaEvent_mt = Class(FA_ResetAreaEvent, Event)
InitEventClass(FA_ResetAreaEvent, "FA_ResetAreaEvent")
function FA_ResetAreaEvent:emptyNew() return Event.new(FA_ResetAreaEvent_mt) end
function FA_ResetAreaEvent:new(object)
    local self = FA_ResetAreaEvent:emptyNew()
    self.object = object
    return self
end
function FA_ResetAreaEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self:run(connection)
end
function FA_ResetAreaEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
end
function FA_ResetAreaEvent:run(connection)
    if self.object and self.object[specName] then
        self.object[specName].areaSession = 0
    end
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
end

FA_SetStripDistanceEvent = {}
local FA_SetStripDistanceEvent_mt = Class(FA_SetStripDistanceEvent, Event)
InitEventClass(FA_SetStripDistanceEvent, "FA_SetStripDistanceEvent")

function FA_SetStripDistanceEvent:emptyNew()
    return Event.new(FA_SetStripDistanceEvent_mt)
end

function FA_SetStripDistanceEvent:new(object, dist)
    local self = FA_SetStripDistanceEvent:emptyNew()
    self.object = object
    self.dist = dist
    return self
end

function FA_SetStripDistanceEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.dist = streamReadFloat32(streamId)
    self:run(connection)
end

function FA_SetStripDistanceEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteFloat32(streamId, self.dist)
end

function FA_SetStripDistanceEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end

    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.tramCenterDistance = self.dist
    end
end

function fahrgassenAutomatik.syncStripDistanceToServer(self)
    if self.isServer then return end

    local spec = self[specName]
    local connection = g_client and g_client:getServerConnection()

    if connection and spec then
        connection:sendEvent(
            FA_SetStripDistanceEvent:new(self, spec.tramCenterDistance)
        )
    end
end

--------------------------------------------------------------------
-- FA_SetStripWidthEvent
--------------------------------------------------------------------
FA_SetStripWidthEvent = {}
local FA_SetStripWidthEvent_mt = Class(FA_SetStripWidthEvent, Event)
InitEventClass(FA_SetStripWidthEvent, "FA_SetStripWidthEvent")

function FA_SetStripWidthEvent:emptyNew()
    return Event.new(FA_SetStripWidthEvent_mt)
end

function FA_SetStripWidthEvent:new(object, width)
    local self = FA_SetStripWidthEvent:emptyNew()
    self.object = object
    self.width  = width
    return self
end

function FA_SetStripWidthEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.width  = streamReadFloat32(streamId)
    self:run(connection)
end

function FA_SetStripWidthEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteFloat32(streamId, self.width)
end

function FA_SetStripWidthEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        self.object[specName].tramHalfWidth = self.width
    end
end

function fahrgassenAutomatik.syncStripWidthToServer(self)
    if self.isServer then return end
    local spec = self[specName]
    local connection = g_client and g_client:getServerConnection()
    if connection and spec then
        connection:sendEvent(FA_SetStripWidthEvent:new(self, spec.tramHalfWidth or 0.6))
    end
end

--------------------------------------------------------------------
-- FA_SetHudPosEvent  — synchronisiert HUD-Position zum Server
--------------------------------------------------------------------
FA_SetHudPosEvent = {}
local FA_SetHudPosEvent_mt = Class(FA_SetHudPosEvent, Event)
InitEventClass(FA_SetHudPosEvent, "FA_SetHudPosEvent")

function FA_SetHudPosEvent:emptyNew()
    return Event.new(FA_SetHudPosEvent_mt)
end

function FA_SetHudPosEvent:new(object, x, y)
    local self = FA_SetHudPosEvent:emptyNew()
    self.object = object
    self.x = x
    self.y = y
    return self
end

function FA_SetHudPosEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.x = streamReadFloat32(streamId)
    self.y = streamReadFloat32(streamId)
    self:run(connection)
end

function FA_SetHudPosEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteFloat32(streamId, self.x)
    streamWriteFloat32(streamId, self.y)
end

function FA_SetHudPosEvent:run(connection)
    if self.object and self.object[specName] then
        self.object[specName].hudPosX = self.x
        self.object[specName].hudPosY = self.y
    end
end

--------------------------------------------------------------------
-- FA_SetTramOffsetEvent — Fahrgassen-Versatz syncen
--------------------------------------------------------------------
FA_SetTramOffsetEvent = {}
local FA_SetTramOffsetEvent_mt = Class(FA_SetTramOffsetEvent, Event)
InitEventClass(FA_SetTramOffsetEvent, "FA_SetTramOffsetEvent")

function FA_SetTramOffsetEvent:emptyNew()
    return Event.new(FA_SetTramOffsetEvent_mt)
end

function FA_SetTramOffsetEvent:new(object, forward, lateral)
    local self = FA_SetTramOffsetEvent:emptyNew()
    self.object  = object
    self.forward = forward
    self.lateral = lateral
    return self
end

function FA_SetTramOffsetEvent:readStream(streamId, connection)
    self.object  = NetworkUtil.readNodeObject(streamId)
    self.forward = streamReadFloat32(streamId)
    self.lateral = streamReadFloat32(streamId)
    self:run(connection)
end

function FA_SetTramOffsetEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteFloat32(streamId, self.forward)
    streamWriteFloat32(streamId, self.lateral)
end

function FA_SetTramOffsetEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.tramForwardOffset = self.forward
        spec.tramLateralOffset = self.lateral
    end
end

local function FA_syncTramOffset(self)
    local spec = self[specName]
    if not spec then return end
    if self.isServer then
        g_server:broadcastEvent(FA_SetTramOffsetEvent:new(self,
            spec.tramForwardOffset or 0,
            spec.tramLateralOffset or 0), nil, nil, self)
    else
        local conn = g_client and g_client:getServerConnection()
        if conn then
            conn:sendEvent(FA_SetTramOffsetEvent:new(self,
                spec.tramForwardOffset or 0,
                spec.tramLateralOffset or 0))
        end
    end
end

--------------------------------------------------------------------
-- FA_SetTramlineTypeEvent
--------------------------------------------------------------------
FA_SetTramlineTypeEvent = {}
local FA_SetTramlineTypeEvent_mt = Class(FA_SetTramlineTypeEvent, Event)
InitEventClass(FA_SetTramlineTypeEvent, "FA_SetTramlineTypeEvent")

function FA_SetTramlineTypeEvent:emptyNew()
    return Event.new(FA_SetTramlineTypeEvent_mt)
end

function FA_SetTramlineTypeEvent:new(object, ttype)
    local self = FA_SetTramlineTypeEvent:emptyNew()
    self.object = object
    self.ttype = ttype
    return self
end

function FA_SetTramlineTypeEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.ttype = streamReadString(streamId)
    self:run(connection)
end

function FA_SetTramlineTypeEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteString(streamId, self.ttype)
end

function FA_SetTramlineTypeEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        self.object[specName].tramlineType = self.ttype
    end
end

local function FA_syncTramlineType(self)
    local spec = self[specName]
    if not spec then return end
    local tt = spec.tramlineType or "cultivated"
    if self.isServer then
        g_server:broadcastEvent(FA_SetTramlineTypeEvent:new(self, tt), nil, nil, self)
    else
        local conn = g_client and g_client:getServerConnection()
        if conn then
            conn:sendEvent(FA_SetTramlineTypeEvent:new(self, tt))
        end
    end
end

--------------------------------------------------------------------
-- FA_ToggleForceTramlineEvent
-- Manuelle Fahrgasse im Pause-Modus ein/ausschalten
--------------------------------------------------------------------
FA_ToggleForceTramlineEvent = {}
local FA_ToggleForceTramlineEvent_mt = Class(FA_ToggleForceTramlineEvent, Event)
InitEventClass(FA_ToggleForceTramlineEvent, "FA_ToggleForceTramlineEvent")

function FA_ToggleForceTramlineEvent:emptyNew()
    return Event.new(FA_ToggleForceTramlineEvent_mt)
end

function FA_ToggleForceTramlineEvent:new(object, active)
    local self = FA_ToggleForceTramlineEvent:emptyNew()
    self.object = object
    self.active = active
    return self
end

function FA_ToggleForceTramlineEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.active = streamReadBool(streamId)
    self:run(connection)
end

function FA_ToggleForceTramlineEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteBool(streamId, self.active)
end

function FA_ToggleForceTramlineEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        self.object[specName].forcePass = self.active
    end
end

--------------------------------------------------------------------
-- FA_ToggleRidgeMarkerSideEvent
-- Manueller Seitenwechsel im auto-Modus: Client → Server → alle Clients
--------------------------------------------------------------------
FA_ToggleRidgeMarkerSideEvent = {}
local FA_ToggleRidgeMarkerSideEvent_mt = Class(FA_ToggleRidgeMarkerSideEvent, Event)
InitEventClass(FA_ToggleRidgeMarkerSideEvent, "FA_ToggleRidgeMarkerSideEvent")

function FA_ToggleRidgeMarkerSideEvent:emptyNew()
    return Event.new(FA_ToggleRidgeMarkerSideEvent_mt)
end

function FA_ToggleRidgeMarkerSideEvent:new(object, side, passCount)
    local self = FA_ToggleRidgeMarkerSideEvent:emptyNew()
    self.object    = object
    self.side      = side
    self.passCount = passCount
    return self
end

function FA_ToggleRidgeMarkerSideEvent:readStream(streamId, connection)
    self.object    = NetworkUtil.readNodeObject(streamId)
    self.side      = streamReadInt8(streamId)
    self.passCount = streamReadInt32(streamId)
    self:run(connection)
end

function FA_ToggleRidgeMarkerSideEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteInt8(streamId,  self.side)
    streamWriteInt32(streamId, self.passCount)
end

function FA_ToggleRidgeMarkerSideEvent:run(connection)
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec._rmForcedSide     = self.side
        spec._rmTotalPassCount = self.passCount
        spec._rmModeChanged    = true
        -- Server: ridgeMarkerState fuer WorkArea setzen.
        -- Client: NICHT direkt forceState - das wuerde den Marker auch
        -- bei eingeklappter/angehobener Maschine ausfahren. Die Animation
        -- uebernimmt onUpdate via _rmModeChanged im naechsten Frame.
        if self.object.isServer then
            local rmSpec = self.object.spec_ridgeMarker
            local safe = rmSpec and rmSpec.ridgeMarkers
            if safe then
                for _, rm in ipairs(rmSpec.ridgeMarkers) do
                    if rm.liftedAnimTime == nil then safe = false break end
                end
            end
            if safe then
                local side = self.side
                if side == 0 then rmSpec.ridgeMarkerState = 1
                elseif side == 1 then rmSpec.ridgeMarkerState = 2
                elseif side == 2 then rmSpec.ridgeMarkerState = 3
                elseif side == 3 then rmSpec.ridgeMarkerState = 1
                end
            end
        end
    end
    -- Server broadcastet an alle anderen Clients
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
end

--------------------------------------------------------------------
-- FA_SetRidgeMarkerModeEvent
--------------------------------------------------------------------
FA_SetRidgeMarkerModeEvent = {}
local FA_SetRidgeMarkerModeEvent_mt = Class(FA_SetRidgeMarkerModeEvent, Event)
InitEventClass(FA_SetRidgeMarkerModeEvent, "FA_SetRidgeMarkerModeEvent")

function FA_SetRidgeMarkerModeEvent:emptyNew()
    return Event.new(FA_SetRidgeMarkerModeEvent_mt)
end

function FA_SetRidgeMarkerModeEvent:new(object, mode)
    local self = FA_SetRidgeMarkerModeEvent:emptyNew()
    self.object = object
    self.mode   = mode
    return self
end

function FA_SetRidgeMarkerModeEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.mode   = streamReadString(streamId)
    self:run(connection)
end

function FA_SetRidgeMarkerModeEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteString(streamId, self.mode)
end

function FA_SetRidgeMarkerModeEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.ridgeMarkerMode = self.mode
        if FA_RidgeMarker then
            if self.mode == "off" then
                spec._rmForcedSide = 0
                FA_RidgeMarker.unhookActionCallback(self.object)
            elseif self.mode == "left" then
                spec._rmForcedSide = 1
                FA_RidgeMarker.hookActionCallback(self.object)
            elseif self.mode == "right" then
                spec._rmForcedSide = 2
                FA_RidgeMarker.hookActionCallback(self.object)
            elseif self.mode == "both" then
                spec._rmForcedSide = 3
                FA_RidgeMarker.hookActionCallback(self.object)
            elseif self.mode == "auto" then
                spec._rmForcedSide = (spec._rmTotalPassCount or 0) % 2 == 1 and 2 or 1
                FA_RidgeMarker.hookActionCallback(self.object)
            end
            -- Server: ridgeMarkerState fuer WorkArea setzen.
            -- Client: NICHT direkt forceState - onUpdate uebernimmt
            -- via _rmModeChanged und beruecksichtigt Fold/Lower korrekt.
            if self.object.isServer then
                local rmSpec = self.object.spec_ridgeMarker
                local safe = rmSpec and rmSpec.ridgeMarkers
                if safe then
                    for _, rm in ipairs(rmSpec.ridgeMarkers) do
                        if rm.liftedAnimTime == nil then safe = false break end
                    end
                end
                if safe then
                    local side = spec._rmForcedSide or 0
                    if side == 0 then rmSpec.ridgeMarkerState = 1
                    elseif side == 1 then rmSpec.ridgeMarkerState = 2
                    elseif side == 2 then rmSpec.ridgeMarkerState = 3
                    elseif side == 3 then rmSpec.ridgeMarkerState = 1
                    end
                end
            end
            spec._rmModeChanged = true
        end
    end
end

local function FA_syncRidgeMarkerMode(self)
    local spec = self[specName]
    if not spec then return end
    if self.isServer then
        g_server:broadcastEvent(FA_SetRidgeMarkerModeEvent:new(self, spec.ridgeMarkerMode or "off"), nil, nil, self)
    else
        local conn = g_client and g_client:getServerConnection()
        if conn then
            conn:sendEvent(FA_SetRidgeMarkerModeEvent:new(self, spec.ridgeMarkerMode or "off"))
        end
    end
end

--------------------------------------------------------------------
-- FA_SetGpsRefEvent — GPS Referenzlinie zum Server syncen
--------------------------------------------------------------------
FA_SetGpsRefEvent = {}
local FA_SetGpsRefEvent_mt = Class(FA_SetGpsRefEvent, Event)
InitEventClass(FA_SetGpsRefEvent, "FA_SetGpsRefEvent")

function FA_SetGpsRefEvent:emptyNew()
    return Event.new(FA_SetGpsRefEvent_mt)
end

function FA_SetGpsRefEvent:new(object, posStr)
    local self = FA_SetGpsRefEvent:emptyNew()
    self.object = object
    self.posStr = posStr
    return self
end

function FA_SetGpsRefEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.posStr = streamReadString(streamId)
    self:run(connection)
end

function FA_SetGpsRefEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteString(streamId, self.posStr or "")
end

function FA_SetGpsRefEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end
    if self.object and self.object[specName] then
        local spec = self.object[specName]
        if self.posStr and self.posStr ~= "" then
            local positions = {}
            for pair in self.posStr:gmatch("[^;]+") do
                local x, z = pair:match("([^,]+),([^,]+)")
                if x and z then
                    table.insert(positions, { x = tonumber(x), z = tonumber(z) })
                end
            end
            if #positions >= 2 then
                spec.gpsRefPositions = positions
                spec.gpsRefSet = true
            end
        else
            spec.gpsRefPositions = nil
            spec.gpsRefSet = false
        end
    end
end

local function FA_positionsToString(positions)
    if not positions then return "" end
    local parts = {}
    for _, p in ipairs(positions) do
        local x, z
        if p.x and p.z then x, z = p.x, p.z
        elseif p[1] and p[2] then x, z = p[1], p[2] end
        if x and z then
            table.insert(parts, string.format("%.4f,%.4f", x, z))
        end
    end
    return table.concat(parts, ";")
end

FA_SetNEvent = {}
local FA_SetNEvent_mt = Class(FA_SetNEvent, Event)
InitEventClass(FA_SetNEvent, "FA_SetNEvent")

function FA_SetNEvent:emptyNew()
    return Event.new(FA_SetNEvent_mt)
end

function FA_SetNEvent:new(object, n)
    local self = FA_SetNEvent:emptyNew()
    self.object = object
    self.n = n
    return self
end

function FA_SetNEvent:readStream(streamId, connection)
    self.object = NetworkUtil.readNodeObject(streamId)
    self.n = streamReadInt8(streamId)
    self:run(connection)
end

function FA_SetNEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.object)
    streamWriteInt8(streamId, self.n)
end

function FA_SetNEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, false, connection, self.object)
    end

    if self.object and self.object[specName] then
        local spec = self.object[specName]
        spec.N = math.max(2, math.min(30, self.n or 2))
        spec.passMax = spec.N
        spec.gassenTarget = math.floor(spec.N / 2) + 1

        -- optional: pass einklemmen
        if spec.pass > spec.passMax then spec.pass = 1 end
    end
end

function fahrgassenAutomatik.syncNToServer(self)
    if self.isServer then return end
    local spec = self[specName]
    local connection = g_client and g_client:getServerConnection()
    if connection and spec then
        connection:sendEvent(FA_SetNEvent:new(self, spec.N))
    end
end
  ------------------------------------------------
-- GLOBAL HUD POSITION (Savegame-File)
------------------------------------------------
fahrgassenAutomatik._globalHudLoaded = false

local function FA_pointInRect(px, py, r)
    return r and px >= r.x and px <= r.x + r.w and py >= r.y and py <= r.y + r.h
end
  ----------------------------------------------------
  -- Pflicht für FS25
  ----------------------------------------------------
  function fahrgassenAutomatik.prerequisitesPresent(specializations)
      return true
  end

  -- Leere Stubs: verhindert GIANTS "Unable to get type nil" beim addSpecialization
  function fahrgassenAutomatik.registerXMLPaths(schema, basePath)
  end

  function fahrgassenAutomatik.registerSavegameXMLPaths(schema, basePath)
  end
  
  function fahrgassenAutomatik.registerEventListeners(vehicleType)
      SpecializationUtil.registerEventListener(vehicleType, "onLoad", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onUpdate", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onDraw", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "saveToXMLFile", fahrgassenAutomatik)
   	  SpecializationUtil.registerEventListener(vehicleType, "onEndWorkAreaProcessing", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onReadStream", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onDeactivate", fahrgassenAutomatik)
      SpecializationUtil.registerEventListener(vehicleType, "onDelete", fahrgassenAutomatik)
  end
  
  function fahrgassenAutomatik:onWriteStream(streamId, connection)
    local spec = self[specName]
    if spec then
        streamWriteInt8(streamId, spec.pass or 1)
        streamWriteInt8(streamId, spec.N or 2)
        streamWriteString(streamId, spec.mode or "Pause")
        streamWriteFloat32(streamId, spec.tramCenterDistance or 2.0)
        streamWriteFloat32(streamId, spec.areaTotal   or 0)
        streamWriteFloat32(streamId, spec.areaSession or 0)
        streamWriteFloat32(streamId, spec.tramHalfWidth or 0.6)
        streamWriteFloat32(streamId, spec.hudPosX or -1)
        streamWriteFloat32(streamId, spec.hudPosY or -1)
        streamWriteInt8(streamId, spec.halfSide or 0)
        streamWriteFloat32(streamId, spec.tramForwardOffset or 0)
        streamWriteFloat32(streamId, spec.tramLateralOffset or 0)
        streamWriteString(streamId,  spec.ridgeMarkerMode   or "off")
        streamWriteInt8(streamId,    spec._rmForcedSide     or 0)
        streamWriteInt32(streamId,   spec._rmTotalPassCount or 0)
        streamWriteString(streamId,  spec.tramlineType      or "cultivated")
        -- ridgeMarkerState direkt mitschicken damit Client exakt denselben
        -- Wert wie Server hat - keine Neuberechnung noetig.
        local rmState = 1
        if self.spec_ridgeMarker then
            rmState = self.spec_ridgeMarker.ridgeMarkerState or 1
        end
        streamWriteInt8(streamId, rmState)
        -- GPS Referenzlinie
        local posStr = (spec.gpsRefSet and spec.gpsRefPositions) and FA_positionsToString(spec.gpsRefPositions) or ""
        streamWriteString(streamId, posStr)
    end
end

function fahrgassenAutomatik:onReadStream(streamId, connection)
    local spec = self[specName]
    if spec then
        spec.pass = streamReadInt8(streamId)
        spec.N = streamReadInt8(streamId)
        spec.mode = streamReadString(streamId)
        spec.tramCenterDistance = streamReadFloat32(streamId)
        spec.areaTotal   = streamReadFloat32(streamId)
        spec.areaSession = streamReadFloat32(streamId)
        spec.tramHalfWidth = streamReadFloat32(streamId)
        local px = streamReadFloat32(streamId)
        local py = streamReadFloat32(streamId)
        if px > 0 then spec.hudPosX = px end
        if py > 0 then spec.hudPosY = py end
        spec.halfSide = streamReadInt8(streamId)
        spec._pendingHalfSideApply = true
        spec.tramForwardOffset = streamReadFloat32(streamId)
        spec.tramLateralOffset = streamReadFloat32(streamId)
        spec.ridgeMarkerMode   = streamReadString(streamId)
        spec._rmForcedSide     = streamReadInt8(streamId)
        spec._rmTotalPassCount = streamReadInt32(streamId)
        spec.tramlineType      = streamReadString(streamId)  -- applyHalfSide nach onPostLoad aufrufen
        -- ridgeMarkerState direkt vom Server uebernehmen und auf Client setzen.
        -- Damit sind Animation und WorkArea vom ersten Frame an identisch.
        local rmStateFromServer = streamReadInt8(streamId)
        if self.spec_ridgeMarker and rmStateFromServer and rmStateFromServer > 0 then
            self.spec_ridgeMarker.ridgeMarkerState = rmStateFromServer
        end
        -- _rmForcedSide aus ridgeMarkerState ableiten fuer die Animation
        if rmStateFromServer == 2 then
            spec._rmForcedSide = 1  -- links
        elseif rmStateFromServer == 3 then
            spec._rmForcedSide = 2  -- rechts
        end
        -- _rmModeChanged setzen damit onUpdate die Animation beim ersten Frame setzt.
        if spec.ridgeMarkerMode ~= "off" and spec._rmForcedSide and spec._rmForcedSide > 0 then
            spec._rmModeChanged = true
        end
        -- GPS Referenzlinie
        local posStr = streamReadString(streamId)
        if posStr and posStr ~= "" then
            local positions = {}
            for pair in posStr:gmatch("[^;]+") do
                local x, z = pair:match("([^,]+),([^,]+)")
                if x and z then
                    table.insert(positions, { x = tonumber(x), z = tonumber(z) })
                end
            end
            if #positions >= 2 then
                spec.gpsRefPositions = positions
                spec.gpsRefSet = true
            end
        end

        spec.passMax = spec.N
        spec.gassenTarget = math.floor(spec.N / 2) + 1
    end
end
----------------------------------------------------
-- onLoad
----------------------------------------------------
function fahrgassenAutomatik:onLoad(savegame)
    if not self.spec_sowingMachine then return end

    local spec = {}
    self[specName] = spec

    ------------------------------------------------
    -- Basisdaten
    ------------------------------------------------
    spec.seedWidth   = 0
    spec.pass        = 1
    spec.N           = 2
    spec.passMax     = spec.N
    spec.gassenTarget = math.floor(spec.N / 2) + 1
    spec.ready       = false
    spec.offsetX     = 1.0
    	-- Abstand der Fahrgassen-Streifen (Mitte-zu-Mitte)
   -- Abstand der Fahrgassen-Streifen: Mitte-zu-Mitte
    spec.tramCenterDistance = 2.00   -- m
    spec.tramCenterStep     = 0.10   -- m
    spec.tramCenterMin      = 1.00   -- m
    spec.tramCenterMax      = 6.00   -- m
  
    spec.offsetZ     = 0.5
    spec.offsetBack  = 1.0
    spec.workAreas   = {}
    spec.actionEvents = {}
	spec.tramHalfWidth = 0.6   -- spurbreite pro seite 
	spec.tramlineType = "cultivated"  -- "cultivated" = Boden aendern, "plants" = nur Pflanzen entfernen
  
    ------------------------------------------------
    -- HUD Position + Drag-State
    ------------------------------------------------
    spec.hudPosX      = nil   -- nil = noch nicht initialisiert → erster Draw setzt Startpos
    spec.hudPosY      = nil
    spec.hudVisible   = true  -- HUD sichtbar/versteckt
    spec.evPassStart  = 1     -- EV-Modus: ab welchem Pass gestartet wird
    spec.areaTotal    = 0     -- gesäte Gesamtfläche m² (bleibt erhalten)
    spec.areaSession  = 0     -- gesäte Sitzungsfläche m² (zurücksetzbar)
    spec._lastPosX    = nil   -- letzter Fahrzeugpos für Distanzberechnung
    spec._lastPosZ    = nil
    spec.hudDragging  = false
    spec.hudDragOffX  = 0
    spec.hudDragOffY  = 0
    spec.hudRect      = nil
    spec.btnClose     = nil
    spec._lastCursorState  = false
    spec.faOwnsCursor      = false
    spec._faSavedRotatableInfo = {}   -- merkt original camera.isRotatable
    spec._faDriveEventsDisabled = false
    ------------------------------------------------
    -- Modus / Status
    ------------------------------------------------
    spec.mode         = "Pause"
    spec.gassenActive = false
    spec.halfSide     = 0
    spec.sowLeftAreas  = {}
    spec.sowRightAreas = {}
    spec.tramLateralOffset = 0
    spec.tramForwardOffset = 0.5
    spec.showOffsetRows    = false
    spec.ridgeMarkerMode   = "off"

    ------------------------------------------------
    -- GPS Arbeitsbreiten-Zähler
    ------------------------------------------------
    spec.gpsRefSet        = false
    spec.gpsSampleDist   = 0.5
    spec._gpsLastSampleX = nil
    spec._gpsLastSampleZ = nil

    ------------------------------------------------
    -- GPS Offset Einstellungen (Default)
    ------------------------------------------------
    spec.gpsLateralOffset = 0
    spec.gpsOffsetStep   = 0
    spec.gpsOffsetMin    = 0
    spec.gpsOffsetMax    = 0

    ------------------------------------------------
    -- Fahrgassen Icon (wird in onPostLoad geladen)
    ------------------------------------------------
    spec.iconTram = nil
    spec.cursorActive = false
    spec.customInputActive = false
    spec._faLastWriteX = nil
    spec._faLastWriteZ = nil
    spec.faWriteDist = 0.2
    spec.faResetTimer = 0
 
    ------------------------------------------------
    -- CP Bereichs-Stabilisierung
    ------------------------------------------------
	spec.cpHeadlandCount = 0
	spec.debugLines = spec.debugLines or {}
	spec._cpRowInfoCache = nil
    spec._cpRowInfoLastTime = 0
	spec._cpHeadlandLockUntil = 0
    spec._cpHeadlandLockData = nil
	spec._cpLastCourse = nil
     spec._cpLastN = nil
    ------------------------------------------------
    -- Savegame laden
    ------------------------------------------------
    if savegame ~= nil then
        local xmlFile = savegame.xmlFile
        local key = savegame.key .. ".FS25_AutomaticTramlineSystem.fahrgassenAutomatik.spec_fahrgassenAutomatik"

        local n = xmlFile:getInt(key .. "#N", nil)
        local aT = xmlFile:getFloat(key .. "#areaTotal",    nil)
        local aS = xmlFile:getFloat(key .. "#areaSession",  nil)
        if aT ~= nil then spec.areaTotal   = aT end
        if aS ~= nil then spec.areaSession = aS end
        local evs = xmlFile:getInt(key .. "#evPassStart", nil)
        if evs ~= nil then spec.evPassStart = math.max(1, math.min(spec.passMax, evs)) end
        if n ~= nil then
            spec.N = math.max(2, math.min(30, n))
            spec.passMax = spec.N
            spec.gassenTarget = math.floor(spec.N / 2) + 1
        end

        local p = xmlFile:getInt(key .. "#pass", nil)
        if p ~= nil then
            spec.pass = math.max(1, math.min(spec.passMax, p))
        end

        local m = xmlFile:getString(key .. "#mode", nil)
        if m ~= nil then spec.mode = m end

        local d = xmlFile:getFloat(key .. "#tramCenterDistance", nil)
        if d ~= nil then
            spec.tramCenterDistance = math.max(spec.tramCenterMin, math.min(spec.tramCenterMax, d))
        end

        local hw = xmlFile:getFloat(key .. "#tramHalfWidth", nil)
        if hw ~= nil then spec.tramHalfWidth = math.max(0.1, math.min(2.0, hw)) end
        local tfo = xmlFile:getFloat(key .. "#tramForwardOffset", nil)
        if tfo ~= nil then spec.tramForwardOffset = tfo end
        local tlo = xmlFile:getFloat(key .. "#tramLateralOffset", nil)
        if tlo ~= nil then spec.tramLateralOffset = tlo end
        local tlt = xmlFile:getString(key .. "#tramlineType", nil)
        if tlt ~= nil then spec.tramlineType = tlt end
        local rmm = xmlFile:getString(key .. "#ridgeMarkerMode", nil)
        if rmm ~= nil then spec.ridgeMarkerMode = rmm end
        local rmc = xmlFile:getInt(key .. "#rmTotalPassCount", nil)
        if rmc ~= nil then spec._rmTotalPassCount = rmc end
        spec._pendingRmInit = true

        local px = xmlFile:getFloat(key .. "#hudPosX", nil)
        local py = xmlFile:getFloat(key .. "#hudPosY", nil)
        if px ~= nil then spec.hudPosX = px end
        if py ~= nil then spec.hudPosY = py end

        local hv = xmlFile:getBool(key .. "#hudVisible", nil)
        if hv ~= nil then spec.hudVisible = hv end

        local hs = xmlFile:getInt(key .. "#halfSide", nil)
        if hs ~= nil then spec.halfSide = math.max(0, math.min(2, hs)) end

        -- GPS Referenzlinie laden
        local gpsRef = xmlFile:getString(key .. ".gpsRefLine#data", nil)
        if gpsRef and gpsRef ~= "" then
            local positions = {}
            for pair in gpsRef:gmatch("[^;]+") do
                local x, z = pair:match("([^,]+),([^,]+)")
                if x and z then
                    table.insert(positions, { x = tonumber(x), z = tonumber(z) })
                end
            end
            if #positions >= 2 then
                spec.gpsRefPositions = positions
                spec.gpsRefSet = true
                spec._pendingGpsRefSync = true
            end
        end

        print(string.format("[FA] Geladen: N=%d pass=%d mode=%s dist=%.2f",
            spec.N, spec.pass, spec.mode, spec.tramCenterDistance))
    end

    print("[FA] onLoad:", self.configFileName)
end
  
  ----------------------------------------------------
  -- WorkArea Breitenmessung
  ----------------------------------------------------
  local function getAreaWidth(wa)
      local sx,_,sz = getWorldTranslation(wa.start)
      local wx,_,wz = getWorldTranslation(wa.width)
      return MathUtil.vector2Length(sx-wx, sz-wz)
  end
  
  ----------------------------------------------------
  -- onPostLoad
  ----------------------------------------------------
  function fahrgassenAutomatik:onPostLoad(savegame)
      local spec = self[specName]
      if not spec then return end
	  
if self.isClient then
    local path = fahrgassenAutomatik.modDir .. "img/fahrgasse.dds"
    spec.iconTram = Overlay.new(path, 0, 0, 1, 1)
    if spec.iconTram ~= nil then
        spec.iconTram:setColor(1,1,1,1)
    end

    spec.hudBgOverlay = g_overlayManager:createOverlay("gui.hudExtension_middle", 0, 0, 0, 0)
    if spec.hudBgOverlay then
        spec.hudBgOverlay:setColor(0, 0, 0, 0.75)
    end

    -- Titelbalken (LS25-Grün)
    spec.hudTitleOverlay = g_overlayManager:createOverlay("gui.hudExtension_middle", 0, 0, 0, 0)
    if spec.hudTitleOverlay then
        spec.hudTitleOverlay:setColor(0.22323, 0.40724, 0.00368, 1.0)  -- LS25 Grün #158525
    end
end

      local waSpec = self.spec_workArea
      if not waSpec or not waSpec.workAreas then return end
  
      local sowAreas = {}
      for _,wa in ipairs(waSpec.workAreas) do
          if FA_isWorkAreaValid(wa) and wa.workAreaType == WorkAreaType.SOWING_MACHINE then
              table.insert(sowAreas, wa)
          end
      end
  
      if #sowAreas == 0 then return end

      local rootNode = self.components[1].node

      ------------------------------------------------
      -- Breite über AI-Marker berechnen (aiMarkerLeft / aiMarkerRight)
      -- Diese sitzen immer an den echten Außenkanten der Maschine
      ------------------------------------------------
      local aiSpec = self.spec_aiImplement or self.spec_aiVehicle
      local leftMarkerNode  = nil
      local rightMarkerNode = nil
      local backMarkerNode  = nil

      if aiSpec and aiSpec.aiMarkers then
          for _, marker in ipairs(aiSpec.aiMarkers) do
              if marker.markerType == AIMarkerType.LEFT  or marker.node and getName(marker.node):lower():find("left")  then leftMarkerNode  = marker.node end
              if marker.markerType == AIMarkerType.RIGHT or marker.node and getName(marker.node):lower():find("right") then rightMarkerNode = marker.node end
              if marker.markerType == AIMarkerType.BACK  or marker.node and getName(marker.node):lower():find("back")  then backMarkerNode  = marker.node end
          end
      end

      -- Fallback: direkt über scenegraph-Node-Namen suchen
      if not leftMarkerNode or not rightMarkerNode then
          local function findNodeByName(node, name)
              if getName(node):lower() == name:lower() then return node end
              for i = 0, getNumOfChildren(node) - 1 do
                  local found = findNodeByName(getChildAt(node, i), name)
                  if found then return found end
              end
              return nil
          end
          local vRoot = self.components[1].node
          if not leftMarkerNode  then leftMarkerNode  = findNodeByName(vRoot, "aiMarkerLeft")  end
          if not rightMarkerNode then rightMarkerNode = findNodeByName(vRoot, "aiMarkerRight") end
          if not backMarkerNode  then backMarkerNode  = findNodeByName(vRoot, "aiMarkerBack")  end
      end

      -- backNode für Fahrgassenposition speichern
      spec.aiBackNode = backMarkerNode
      if backMarkerNode then
          print("[FA] aiBackNode gefunden: " .. getName(backMarkerNode))
      else
          print("[FA] aiBackNode nicht gefunden - nutze WA-Berechnung")
      end

      -- Lokale X der Marker im Fahrzeugsystem
      local leftLX, rightLX = nil, nil
      if leftMarkerNode then
          local wx,wy,wz = getWorldTranslation(leftMarkerNode)
          leftLX,_,_ = worldToLocal(rootNode, wx, wy, wz)
      end
      if rightMarkerNode then
          local wx,wy,wz = getWorldTranslation(rightMarkerNode)
          rightLX,_,_ = worldToLocal(rootNode, wx, wy, wz)
      end

      -- Breite und Mitte bestimmen
      local totalWidth, midLX
      if leftLX and rightLX then
          -- links hat größeres localX (positiv), rechts kleineres (negativ)
          local lx = math.max(leftLX, rightLX)
          local rx = math.min(leftLX, rightLX)
          totalWidth = math.abs(lx - rx)
          midLX = (lx + rx) * 0.5
      else
          -- Fallback auf WA-Nodes min/max
          local minLX =  math.huge
          local maxLX = -math.huge
          for _, wa in ipairs(sowAreas) do
              local sn = wa.start or wa.startNode
              local wn = wa.width or wa.widthNode
              if sn then local wx,wy,wz = getWorldTranslation(sn) local lx,_,_ = worldToLocal(rootNode, wx, wy, wz) if lx < minLX then minLX = lx end if lx > maxLX then maxLX = lx end end
              if wn then local wx,wy,wz = getWorldTranslation(wn) local lx,_,_ = worldToLocal(rootNode, wx, wy, wz) if lx < minLX then minLX = lx end if lx > maxLX then maxLX = lx end end
          end
          totalWidth = math.abs(maxLX - minLX)
          midLX = (maxLX + minLX) * 0.5
      end

      spec.seedWidth = totalWidth
      spec.ready = totalWidth > 0
      -- Mitte der AB im Fahrzeugsystem merken
      spec.abMidLocalX = midLX

      -- ⭐ Fahrzeug-spezifische Überschreibungen
      -- Format: { width = X, offsetX = Y }  (offsetX positiv = rechts)
      local vehicleOverrides = {
          ["SeedingBarLeft"] = { width = 8.0, offsetX = -2.0 },
      }
      local configName = self.configFileName or ""
      for pattern, override in pairs(vehicleOverrides) do
          if configName:find(pattern) then
              spec.seedWidth        = override.width
              spec.abMidLocalX      = 0
              spec.tramLateralOffset = override.offsetX or 0
              print(string.format("[FA] Fahrzeug-Override: %s → width=%.1f offsetX=%.1f", pattern, spec.seedWidth, spec.tramLateralOffset))
              break
          end
      end

      ------------------------------------------------
      -- Originalpositionen der WA-Nodes speichern
      -- 1 WA  → halfX/halfZ (halbe WA-Breite)
      -- >1 WA → AI-Marker Mitte, jede WA auf Gesamtmitte zuschneiden
      ------------------------------------------------
      spec.sowAreaData = {}
      local multiWA = (#sowAreas > 1)

      -- Bei mehreren WAs: Gesamtmitte über AI-Marker berechnen
      local midLX = nil
      if multiWA then
          local aiSpec2 = self.spec_aiImplement or self.spec_aiVehicle
          if aiSpec2 and aiSpec2.aiMarkers then
              local llx, rlx = nil, nil
              for _, marker in ipairs(aiSpec2.aiMarkers) do
                  if marker.node then
                      local wx2,wy2,wz2 = getWorldTranslation(marker.node)
                      local mlx,_,_ = worldToLocal(rootNode, wx2, wy2, wz2)
                      local name = getName(marker.node):lower()
                      if name:find("left")  then llx = mlx end
                      if name:find("right") then rlx = mlx end
                  end
              end
              if llx and rlx then
                  midLX = (math.max(llx,rlx) + math.min(llx,rlx)) * 0.5
                  print(string.format("[FA] Multi-WA Mitte via AI-Marker: midLX=%.3f", midLX))
              end
          end
          -- Fallback: Mitte aus allen WA-Nodes
          if not midLX then
              local minLX2, maxLX2 = math.huge, -math.huge
              for _, wa in ipairs(sowAreas) do
                  local sn = wa.start or wa.startNode
                  local wn = wa.width or wa.widthNode
                  if sn then local wx2,wy2,wz2 = getWorldTranslation(sn) local lx,_,_ = worldToLocal(rootNode, wx2,wy2,wz2) if lx < minLX2 then minLX2=lx end if lx > maxLX2 then maxLX2=lx end end
                  if wn then local wx2,wy2,wz2 = getWorldTranslation(wn) local lx,_,_ = worldToLocal(rootNode, wx2,wy2,wz2) if lx < minLX2 then minLX2=lx end if lx > maxLX2 then maxLX2=lx end end
              end
              midLX = (minLX2 + maxLX2) * 0.5
              print(string.format("[FA] Multi-WA Mitte via Nodes: midLX=%.3f", midLX))
          end
          spec.abMidLocalX = midLX
      end

      for i, wa in ipairs(sowAreas) do
          local startNode  = wa.start  or wa.startNode
          local widthNode  = wa.width  or wa.widthNode
          local heightNode = wa.height or wa.heightNode

          if startNode and widthNode and heightNode then
              local sx, sy, sz = getTranslation(startNode)
              local wx, wy, wz = getTranslation(widthNode)
              local hx, hy, hz = getTranslation(heightNode)

              local halfX, halfZ, midSX, midWX, midHX

              if not multiWA then
                  -- 1 WA: halbe eigene Breite
                  halfX = (wx - sx) * 0.5
                  halfZ = (wz - sz) * 0.5
              else
                  -- Mehrere WAs: Mitte via AI-Marker
                  local swx,swy,swz = getWorldTranslation(startNode)
                  local wwx,wwy,wwz = getWorldTranslation(widthNode)
                  local slx,_,_ = worldToLocal(rootNode, swx, swy, swz)
                  local wlx,_,_ = worldToLocal(rootNode, wwx, wwy, wwz)
                  midSX = sx + (midLX - slx)
                  midWX = wx + (midLX - wlx)
                  midHX = hx + (midLX - slx)
              end

              table.insert(spec.sowAreaData, {
                  wa          = wa,
                  startNode   = startNode,
                  widthNode   = widthNode,
                  heightNode  = heightNode,
                  origSX = sx, origSY = sy, origSZ = sz,
                  origWX = wx, origWY = wy, origWZ = wz,
                  origHX = hx, origHY = hy, origHZ = hz,
                  -- 1 WA
                  halfX  = halfX,
                  halfZ  = halfZ,
                  -- Multi-WA
                  midSX  = midSX,
                  midWX  = midWX,
                  midHX  = midHX,
                  multiWA = multiWA,
                  startIsLeft = multiWA and (({worldToLocal(rootNode, getWorldTranslation(startNode))})[1] > ({worldToLocal(rootNode, getWorldTranslation(widthNode))})[1]) or false,
              })

              if not multiWA then
                  print(string.format("[FA] WA[%d] 1-WA: startX=%.3f widthX=%.3f halfX=%.3f", i, sx, wx, halfX))
              else
                  print(string.format("[FA] WA[%d] Multi-WA: midSX=%.3f midWX=%.3f", i, midSX, midWX))
              end
          end
      end

      -- halfSide: 0=beide, 1=linkeHälfte, 2=rechteHälfte
      spec.halfSide = spec.halfSide or 0
	  
      -- GPS Offset Grenzen JETZT korrekt setzen
      spec.gpsOffsetStep = spec.seedWidth * 0.5     -- halbe Arbeitsbreite
      spec.gpsOffsetMin  = -spec.seedWidth          -- -1 Spur
      spec.gpsOffsetMax  =  spec.seedWidth          -- +1 Spur
      
      -- Sicherheit: Offset einklemmen
      if spec.gpsLateralOffset < spec.gpsOffsetMin then
          spec.gpsLateralOffset = spec.gpsOffsetMin
      elseif spec.gpsLateralOffset > spec.gpsOffsetMax then
          spec.gpsLateralOffset = spec.gpsOffsetMax
      end

if self.isServer and #spec.workAreas == 0 then
    fahrgassenAutomatik.createGassen(self)
end
      print(("[FA] POSTLOAD: WA=%d  width=%.2f m  sowAreaData=%d"):format(#sowAreas, totalWidth, #spec.sowAreaData))

      -- halfSide wird im ersten onUpdate angewendet wenn Nodes korrekt in der Welt stehen
      if (spec.halfSide or 0) ~= 0 then
          spec._pendingHalfSideApply = true
      end
  end
  
  ----------------------------------------------------
  -- Gassen erzeugen
  ----------------------------------------------------
  local WorkArea = WorkArea
  
  function fahrgassenAutomatik.createGassen(self)
      local spec = self[specName]
      local root = self.components[1].node
  
      if not self.spec_workArea or not self.spec_workArea.workAreas then
          return
      end
  
      local function makeArea(sign)
          local start = createTransformGroup("faStart"..sign)
          local width = createTransformGroup("faWidth"..sign)
          local height = createTransformGroup("faHeight"..sign)
  
          link(root, start)
          link(root, width)
          link(root, height)
  
          -- Position
          setTranslation(start, sign*spec.offsetX, -0.20, spec.offsetZ)
          setTranslation(width, sign*spec.offsetX + 0.01, -0.20, spec.offsetZ)
          setTranslation(height, sign*spec.offsetX, -0.20, spec.offsetZ + 0.01)
  
          -- WorkArea Table (FS25 Style)
          local wa = {
              startNode = start,
              widthNode = width,
              heightNode = height,
              workAreaType = WorkAreaType.CULTIVATOR,
              foldMinLimit = 0,
              foldMaxLimit = 1,
              disabled = true
          }

          -- NICHT in die GIANTS-Engine-Tabelle (self.spec_workArea.workAreas)
          -- einfuegen! DLC-Erweiterungen wie Sky Agriculture WorkEffects
          -- iterieren ueber diese Tabelle und erwarten z.B. eine 1:1
          -- Korrelation zu RidgeMarker-Eintraegen. Eine zusaetzliche WorkArea
          -- ohne RidgeMarker-Pendant verschiebt den Index und crasht dort.

          -- Für unsere eigene Liste
          table.insert(spec.workAreas, wa)
      end
  
      -- Links + Rechts
      makeArea(1)
      makeArea(-1)
  end
  
  ----------------------------------------------------
  -- setGassen
  ----------------------------------------------------
function fahrgassenAutomatik.setGassen(self, enable)
    local spec = self[specName]

    -- ⭐ CLIENT → Server informieren
    if not self.isServer then
        fahrgassenAutomatik.syncGassenToServer(self, enable)
        return
    end

    -- ⭐ SERVER setzt wirklich
    for _,wa in ipairs(spec.workAreas) do
        wa.disabled = not enable
    end

    spec.gassenActive = enable
end

----------------------------------------------------
-- applyHalfSide: WA-Nodes an AB-Gesamtmitte (via AI-Marker) clippen
-- halfSide: 0=beide, 1=linkeHälfte, 2=rechteHälfte
----------------------------------------------------
function fahrgassenAutomatik.applyHalfSide(self)
    local spec = self[specName]
    if not spec or not spec.sowAreaData then return end

    local hs = spec.halfSide or 0

    for _, d in ipairs(spec.sowAreaData) do
        if hs == 0 then
            setTranslation(d.startNode,  d.origSX, d.origSY, d.origSZ)
            setTranslation(d.widthNode,  d.origWX, d.origWY, d.origWZ)
            setTranslation(d.heightNode, d.origHX, d.origHY, d.origHZ)

        elseif not d.multiWA then
            -- 1 WA: um halbe eigene Breite verschieben
            if hs == 1 then
                setTranslation(d.startNode,  d.origSX,           d.origSY, d.origSZ)
                setTranslation(d.widthNode,  d.origWX - d.halfX, d.origWY, d.origWZ - d.halfZ)
                setTranslation(d.heightNode, d.origHX,           d.origHY, d.origHZ)
            elseif hs == 2 then
                setTranslation(d.startNode,  d.origSX + d.halfX, d.origSY, d.origSZ + d.halfZ)
                setTranslation(d.widthNode,  d.origWX,           d.origWY, d.origWZ)
                setTranslation(d.heightNode, d.origHX + d.halfX, d.origHY, d.origHZ + d.halfZ)
            end

        else
            -- Multi-WA: auf AI-Marker Mitte zuschneiden
            if hs == 1 then
                if d.startIsLeft then
                    setTranslation(d.startNode,  d.origSX, d.origSY, d.origSZ)
                    setTranslation(d.widthNode,  d.midWX,  d.origWY, d.origWZ)
                    setTranslation(d.heightNode, d.origHX, d.origHY, d.origHZ)
                else
                    setTranslation(d.startNode,  d.midSX,  d.origSY, d.origSZ)
                    setTranslation(d.widthNode,  d.origWX, d.origWY, d.origWZ)
                    setTranslation(d.heightNode, d.midHX,  d.origHY, d.origHZ)
                end
            elseif hs == 2 then
                if d.startIsLeft then
                    setTranslation(d.startNode,  d.midSX,  d.origSY, d.origSZ)
                    setTranslation(d.widthNode,  d.origWX, d.origWY, d.origWZ)
                    setTranslation(d.heightNode, d.midHX,  d.origHY, d.origHZ)
                else
                    setTranslation(d.startNode,  d.origSX, d.origSY, d.origSZ)
                    setTranslation(d.widthNode,  d.midWX,  d.origWY, d.origWZ)
                    setTranslation(d.heightNode, d.origHX, d.origHY, d.origHZ)
                end
            end
        end
    end
end

function fahrgassenAutomatik.syncHalfSideToServer(self)
    local spec = self[specName]
    if not spec then return end
    local connection = g_client and g_client:getServerConnection()
    if connection then
        connection:sendEvent(FA_SetHalfSideEvent:new(self, spec.halfSide or 0))
    end
end

function fahrgassenAutomatik.actionToggleHalfSide(self)
    local spec = self[specName]
    if not spec then return end

    -- 0(beide) → 1(links) → 2(rechts) → 0
    spec.halfSide = (spec.halfSide + 1) % 3

    fahrgassenAutomatik.applyHalfSide(self)

    if self.isServer then
        g_server:broadcastEvent(FA_SetHalfSideEvent:new(self, spec.halfSide), nil, nil, self)
    else
        fahrgassenAutomatik.syncHalfSideToServer(self)
    end
end
  
  ----------------------------------------------------
  -- Imputbindings
  ----------------------------------------------------
  function fahrgassenAutomatik:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
      if not self.isClient then return end
  
      local spec = self[specName]
      if not spec or not spec.ready then return end
      if not isActiveForInputIgnoreSelection then return end
  
      spec.actionEvents = spec.actionEvents or {}
      self:clearActionEventsTable(spec.actionEvents)

     local _, ev1 = self:addActionEvent(spec.actionEvents, InputAction.FA_INCREASE, self, fahrgassenAutomatik.actionIncrease, false, true, false, true)
  local _, ev2 = self:addActionEvent(spec.actionEvents, InputAction.FA_DECREASE, self, fahrgassenAutomatik.actionDecrease, false, true, false, true)
  
  if ev1 then
      g_inputBinding:setActionEventText(ev1, FA_l10n("input_FA_INCREASE"))
      g_inputBinding:setActionEventTextVisibility(ev1, true)
      g_inputBinding:setActionEventActive(ev1, true)
      g_inputBinding:setActionEventTextPriority(ev1, GS_PRIO_NORMAL)
  end
  
  if ev2 then
      g_inputBinding:setActionEventText(ev2, FA_l10n("input_FA_DECREASE"))
      g_inputBinding:setActionEventTextVisibility(ev2, true)
      g_inputBinding:setActionEventActive(ev2, true)
      g_inputBinding:setActionEventTextPriority(ev2, GS_PRIO_NORMAL)
  end

  local _, evToggle = self:addActionEvent(spec.actionEvents, InputAction.TOGGLE_CURSOR, self, fahrgassenAutomatik.toggleCursor, false, true, false, true)
  g_inputBinding:setActionEventTextVisibility(evToggle, false)
  
  local _, evMode = self:addActionEvent(spec.actionEvents, InputAction.FA_TOGGLE_MODE, self, fahrgassenAutomatik.actionToggleMode, false, true, false, true)
  g_inputBinding:setActionEventText(evMode, FA_l10n("input_FA_TOGGLE_MODE"))
  g_inputBinding:setActionEventActive(evMode, true)

  local _, evResetArea = self:addActionEvent(spec.actionEvents, InputAction.FA_RESET_AREA, self, fahrgassenAutomatik.actionResetArea, false, true, false, true)
  g_inputBinding:setActionEventText(evResetArea, FA_l10n("input_FA_RESET_AREA"))
  g_inputBinding:setActionEventActive(evResetArea, true)

  local _, evPassPlus = self:addActionEvent(spec.actionEvents, InputAction.FA_PASS_PLUS, self, fahrgassenAutomatik.actionPassPlus, false, true, false, true)
  g_inputBinding:setActionEventText(evPassPlus, FA_l10n("input_FA_PASS_PLUS"))
  g_inputBinding:setActionEventActive(evPassPlus, true)

  local _, evPassMinus = self:addActionEvent(spec.actionEvents, InputAction.FA_PASS_MINUS, self, fahrgassenAutomatik.actionPassMinus, false, true, false, true)
  g_inputBinding:setActionEventText(evPassMinus, FA_l10n("input_FA_PASS_MINUS"))
  g_inputBinding:setActionEventActive(evPassMinus, true)
  
  local _, evRef = self:addActionEvent(spec.actionEvents, InputAction.FA_SET_GPS_REF, self, fahrgassenAutomatik.actionSetGpsRef, false, true, false, true)
     
     if evRef then
         g_inputBinding:setActionEventText(evRef, FA_l10n("input_FA_SET_GPS_REF"))
         g_inputBinding:setActionEventActive(evRef, true)
     end
     
     local _, evW = self:addActionEvent(spec.actionEvents, InputAction.FA_STRIP_WIDER, self, fahrgassenAutomatik.actionStripWider, false, true, false, true)
     local _, evN = self:addActionEvent(spec.actionEvents, InputAction.FA_STRIP_NARROWER, self, fahrgassenAutomatik.actionStripNarrower, false, true, false, true)
     
     if evW then
         g_inputBinding:setActionEventText(evW, FA_l10n("input_FA_STRIP_WIDER"))
         g_inputBinding:setActionEventTextVisibility(evW, true)
         g_inputBinding:setActionEventActive(evW, true)
         g_inputBinding:setActionEventTextPriority(evW, GS_PRIO_NORMAL)
     end
     
     if evN then
         g_inputBinding:setActionEventText(evN, FA_l10n("input_FA_STRIP_NARROWER"))
         g_inputBinding:setActionEventTextVisibility(evN, true)
         g_inputBinding:setActionEventActive(evN, true)
         g_inputBinding:setActionEventTextPriority(evN, GS_PRIO_NORMAL)
     end

  local _, evHalf = self:addActionEvent(spec.actionEvents, InputAction.FA_TOGGLE_HALF, self, fahrgassenAutomatik.actionToggleHalfSide, false, true, false, true)
  if evHalf then
      g_inputBinding:setActionEventText(evHalf, FA_l10n("input_FA_TOGGLE_HALF"))
      g_inputBinding:setActionEventTextVisibility(evHalf, true)
      g_inputBinding:setActionEventActive(evHalf, true)
      g_inputBinding:setActionEventTextPriority(evHalf, GS_PRIO_NORMAL)
  end
  
  -- IMPLEMENT_EXTRA4 für manuellen RM-Seitenwechsel im auto-Modus
  local _, evRmToggle = self:addActionEvent(spec.actionEvents, InputAction.IMPLEMENT_EXTRA4, self,
      function(vehicle)
          if FA_RidgeMarker then
              FA_RidgeMarker.toggleSide(vehicle)
          end
      end, true, false, false, true)
  if evRmToggle then
      g_inputBinding:setActionEventTextVisibility(evRmToggle, false)
  end

  -- IMPLEMENT_EXTRA3 für manuelles Fahrgasse ein/ausschalten im Pause-Modus
  local _, evForceTram = self:addActionEvent(spec.actionEvents, InputAction.FA_FORCE_TRAM, self,
      function(vehicle)
          local s = vehicle[specName]
          if not s then return end
          s.forcePass = not s.forcePass
          -- Server und andere Clients synchronisieren
          if FA_ToggleForceTramlineEvent then
              if vehicle.isServer then
                  g_server:broadcastEvent(FA_ToggleForceTramlineEvent:new(vehicle, s.forcePass), nil, nil, vehicle)
              else
                  local conn = g_client and g_client:getServerConnection()
                  if conn then conn:sendEvent(FA_ToggleForceTramlineEvent:new(vehicle, s.forcePass)) end
              end
          end
      end, true, false, false, true)
  if evForceTram then
      g_inputBinding:setActionEventTextVisibility(evForceTram, true)
      g_inputBinding:setActionEventText(evForceTram, FA_l10n("input_FA_FORCE_TRAM"))
      g_inputBinding:setActionEventActive(evForceTram, true)
      g_inputBinding:setActionEventTextPriority(evForceTram, GS_PRIO_NORMAL)
  end
  end
  
  -- Kamera-Rotation per Maus togglen
local function FA_setCameraRotation(vehicle, enableRotation, savedRotatableInfo)
    if not vehicle or not vehicle.spec_enterable or not vehicle.spec_enterable.cameras then
        return
    end
    savedRotatableInfo = savedRotatableInfo or {}

    for _, camera in pairs(vehicle.spec_enterable.cameras) do
        if enableRotation then
            -- restore original (default true wenn unbekannt)
            local restore = savedRotatableInfo[camera]
            if restore == nil then restore = true end
            camera.isRotatable = restore
        else
            -- save + disable
            savedRotatableInfo[camera] = camera.isRotatable
            camera.isRotatable = false
        end
    end
end

function fahrgassenAutomatik.toggleCursor(self)
    local spec = self[specName]
    local input = g_inputBinding
    local root = self.rootVehicle or self
    local currentVehicle = g_localPlayer and g_localPlayer:getCurrentVehicle()

    if currentVehicle ~= self and currentVehicle ~= root then
        return
    end

    spec.cursorActive = not spec.cursorActive

    if spec.cursorActive then
        input:setShowMouseCursor(true)
        spec.hudVisible = true   -- HUD beim Öffnen des Cursors immer einblenden

        -- 1) Kamera-Rotation aus
        FA_setCameraRotation(root, false, spec._faSavedRotatableInfo)

    else
        input:setShowMouseCursor(false)
        spec.hudDragging = false

        -- 1) Kamera-Rotation zurück
        FA_setCameraRotation(root, true, spec._faSavedRotatableInfo)

        -- 2) Fahr-Input zurück (nur wenn wir was deaktiviert hatten)
        if spec._faDriveEventsDisabled then
       
            spec._faDriveEventsDisabled = false
        end

    end
end
  

 ----------------------------------------------------
  -- onUpdate
  ----------------------------------------------------
 function fahrgassenAutomatik:onUpdate(dt)

    local spec = self[specName]
    if not spec or not spec.ready then
        return
    end

  local root = self.rootVehicle or self

  -- RidgeMarker Zustand halten (GIANTS Automatik unterdrücken)
  if self.isClient and FA_RidgeMarker then
      FA_RidgeMarker.onUpdate(self)
  end
      local connection = g_client and g_client:getServerConnection()
      if connection then
          local posStr = FA_positionsToString(spec.gpsRefPositions)
          connection:sendEvent(FA_SetGpsRefEvent:new(self, posStr))
          spec._pendingGpsRefSync = false
      end


  -- halfSide nach Load anwenden sobald Nodes korrekt in der Welt stehen
  if spec._pendingHalfSideApply and spec.sowAreaData and #spec.sowAreaData > 0 then
      fahrgassenAutomatik.applyHalfSide(self)
      spec._pendingHalfSideApply = false
  end

  -- RidgeMarker-Init nach Load wird komplett von onUpdate erledigt:
  -- der _rmForcedSide==nil-Block in FA_RidgeMarker.onUpdate prueft
  -- Fold/Lower-Status und setzt den Marker korrekt.

  -- DEBUG: gpsRefSet Zustand tracken (einmalig nach Load)
  if spec._gpsRefDebugCount == nil then spec._gpsRefDebugCount = 0 end
  if spec._gpsRefDebugCount < 5 then
      spec._gpsRefDebugCount = spec._gpsRefDebugCount + 1
  end
  
  	--------------------------------------------
      -- LOWER-DETECTOR (kein GPS aktiv)
      --------------------------------------------------------------------
if spec.mode == "Manuel" then
    local isLowered = self:getIsImplementChainLowered()

    if spec._lastLowerState == nil then
        spec._lastLowerState = isLowered
    end

    -- ⭐ WICHTIG: nach Modewechsel (pass=0) sauber starten
    if self.isServer and (spec.pass == nil or spec.pass <= 0) then
        spec.pass = 1
        -- optional: Clients direkt updaten, damit alle gleich starten
        fahrgassenAutomatik.onPassChanged(self)
        g_server:broadcastEvent(FA_SetPassEvent:new(self, spec.pass), nil, nil, self)
    end

    if self.isServer then
        if spec._lastLowerState == true and isLowered == false then
            spec.pass = spec.pass + 1
            if spec.pass > spec.passMax then
                spec.pass = 1
            end

            local target = math.floor(spec.N / 2) + 1
            fahrgassenAutomatik.setGassen(self, spec.pass == target)

            fahrgassenAutomatik.onPassChanged(self)
        g_server:broadcastEvent(FA_SetPassEvent:new(self, spec.pass), nil, nil, self)
        end
    end

    spec._lastLowerState = isLowered
end

local currentVehicle = g_localPlayer and g_localPlayer:getCurrentVehicle()
local isControlled = (currentVehicle == self or currentVehicle == root)

    -- HUD wird jetzt in onDraw() gezeichnet – hier kein addHelpExtension mehr noetig
        	 if spec.faResetTimer and spec.faResetTimer > 0 then
            spec.faResetTimer = math.max(0, spec.faResetTimer - dt)
        end
    ------------------------------------------------
    -- ⭐ Klick-Logik darf NUR laufen wenn Spieler im Traktor sitzt
    ------------------------------------------------
	if isControlled then
        -- Safety: wenn Cursor extern aus ist, alles restore
        if spec.cursorActive and not g_inputBinding:getShowMouseCursor() then
            spec.cursorActive = false
            spec.hudDragging = false
        
            local root = self.rootVehicle or self
            FA_setCameraRotation(root, true, spec._faSavedRotatableInfo)
            if spec._faDriveEventsDisabled then
                spec._faDriveEventsDisabled = false
            end
        
        end
             ------------------------------------------------
             -- CURSOR SYNC (robust gegen Courseplay)
             ------------------------------------------------
             local cursorNow = g_inputBinding:getShowMouseCursor()
             
             if spec.faOwnsCursor and not cursorNow and spec._lastCursorState then
                 g_inputBinding:revertContext(false)
             
                 spec.customInputActive = false
                 spec.cursorActive = false
                 spec.hudDragging = false
             
             end
        
             spec._lastCursorState = cursorNow
             spec.faOwnsCursor = false
              local mx, my = g_lastMousePosX, g_lastMousePosY
             
             local mouseDown = Input.isMouseButtonPressed(Input.MOUSE_BUTTON_LEFT)
             local mousePressed = mouseDown and not spec._mouseWasDown
             spec._mouseWasDown = mouseDown
             
             -- 0. PRIORITÄT → CLOSE BUTTON (X)
             if mousePressed and spec.btnClose and FA_pointInRect(mx, my, spec.btnClose) then
                 spec.hudVisible = false
                 spec.hudDragging = false
                 -- Cursor schließen
                 g_inputBinding:setShowMouseCursor(false)
                 spec.cursorActive = false
                 FA_setCameraRotation(self.rootVehicle or self, true, spec._faSavedRotatableInfo)
                 return
             end

             -- 1. PRIORITÄT → MODE BUTTON
             local modeClicked = false
           if mousePressed and spec.modeHit then
            local bx, by, bw, bh = spec.modeHit.x, spec.modeHit.y, spec.modeHit.w, spec.modeHit.h
            if mx >= bx and mx <= bx + bw and my >= by and my <= by + bh then
        
            local newMode = FA_getNextMode(self, spec.mode)
            
            spec.mode = newMode
            spec.pass = 0
            spec.gpsRefSet = false
            spec.gpsRefPositions = nil
            -- Löschen auch zum Server syncen
            if self.isServer then
                g_server:broadcastEvent(FA_SetGpsRefEvent:new(self, ""), nil, nil, self)
            else
                local connection = g_client and g_client:getServerConnection()
                if connection then connection:sendEvent(FA_SetGpsRefEvent:new(self, "")) end
            end
			spec.cpHeadlandCount = 0
            fahrgassenAutomatik.resetGassenState(self)
        
                -- ⭐ EINMAL sync
                fahrgassenAutomatik.syncPassToServer(self)
                fahrgassenAutomatik.syncModeToServer(self)
                modeClicked = true
            end
        end
               
       -- 2. WENN MODE KLICK → KEIN DRAG
       if modeClicked then
           spec.hudDragging = false
           return
       end
     
   -- PLUS / MINUS BUTTONS
  if mousePressed and not modeClicked then
  
      ------------------------------------------------
      -- MINUS
      ------------------------------------------------
      if spec.btnMinus
      and mx >= spec.btnMinus.x and mx <= spec.btnMinus.x + spec.btnMinus.w
      and my >= spec.btnMinus.y and my <= spec.btnMinus.y + spec.btnMinus.h then
  
          if spec.mode == "GPS" then
              -- GPS → OFFSET
              spec.gpsLateralOffset = spec.gpsLateralOffset - spec.gpsOffsetStep
  
              if spec.gpsLateralOffset < spec.gpsOffsetMin then
                  spec.gpsLateralOffset = spec.gpsOffsetMin
              end
  
          elseif spec.mode == "EnhancedVehicle" then
              -- EV → Startpass direkt einstellen
              spec.evPassStart = spec.evPassStart or 1
              spec.evPassStart = spec.evPassStart - 1
              if spec.evPassStart < 1 then spec.evPassStart = spec.passMax end
          else
              -- ANDERE MODI → PASS
              if self.isServer then
                  spec.pass = spec.pass - 1
                  if spec.pass < 1 then spec.pass = spec.passMax end
                  fahrgassenAutomatik.setGassen(self, spec.pass == spec.gassenTarget)
                  fahrgassenAutomatik.onPassChanged(self)
        g_server:broadcastEvent(FA_SetPassEvent:new(self, spec.pass), nil, nil, self)
              else
                  local newPass = spec.pass - 1
                  if newPass < 1 then newPass = spec.passMax end
                  spec.pass = newPass  -- sofort lokal für HUD-Anzeige
                  fahrgassenAutomatik.onPassChanged(self)
                  g_client:getServerConnection():sendEvent(FA_SetPassEvent:new(self, newPass))
              end
          end
      end
  
      ------------------------------------------------
      -- PLUS
      ------------------------------------------------
      if spec.btnPlus
      and mx >= spec.btnPlus.x and mx <= spec.btnPlus.x + spec.btnPlus.w
      and my >= spec.btnPlus.y and my <= spec.btnPlus.y + spec.btnPlus.h then
  
          if spec.mode == "GPS" then
              -- GPS → OFFSET
              spec.gpsLateralOffset = spec.gpsLateralOffset + spec.gpsOffsetStep
  
              if spec.gpsLateralOffset > spec.gpsOffsetMax then
                  spec.gpsLateralOffset = spec.gpsOffsetMax
              end
  
          elseif spec.mode == "EnhancedVehicle" then
              -- EV → Startpass direkt einstellen
              spec.evPassStart = spec.evPassStart or 1
              spec.evPassStart = spec.evPassStart + 1
              if spec.evPassStart > spec.passMax then spec.evPassStart = 1 end
          else
              -- ANDERE MODI → PASS
              if self.isServer then
                  spec.pass = spec.pass + 1
                  if spec.pass > spec.passMax then spec.pass = 1 end
                  fahrgassenAutomatik.setGassen(self, spec.pass == spec.gassenTarget)
                  fahrgassenAutomatik.onPassChanged(self)
        g_server:broadcastEvent(FA_SetPassEvent:new(self, spec.pass), nil, nil, self)
              else
                  local newPass = spec.pass + 1
                  if newPass > spec.passMax then newPass = 1 end
                  spec.pass = newPass  -- sofort lokal für HUD-Anzeige
                  fahrgassenAutomatik.onPassChanged(self)
                  g_client:getServerConnection():sendEvent(FA_SetPassEvent:new(self, newPass))
              end
          end
      end
	  
     	  ------------------------------------------------
     -- FAHRGASSENABSTAND - 
     ------------------------------------------------
     if spec.btnNMinus
     and mx >= spec.btnNMinus.x and mx <= spec.btnNMinus.x + spec.btnNMinus.w
     and my >= spec.btnNMinus.y and my <= spec.btnNMinus.y + spec.btnNMinus.h then
     
         spec.N = math.max(2, spec.N - 1)
         spec.passMax = spec.N
         spec.gassenTarget = math.floor(spec.N / 2) + 1
         fahrgassenAutomatik.syncNToServer(self)
     end
     
     ------------------------------------------------
     -- FAHRGASSENABSTAND +
     ------------------------------------------------
     if spec.btnNPlus
     and mx >= spec.btnNPlus.x and mx <= spec.btnNPlus.x + spec.btnNPlus.w
     and my >= spec.btnNPlus.y and my <= spec.btnNPlus.y + spec.btnNPlus.h then
     
         spec.N = math.min(30, spec.N + 1)
         spec.passMax = spec.N
         spec.gassenTarget = math.floor(spec.N / 2) + 1
     fahrgassenAutomatik.syncNToServer(self)
     end
	 ------------------------------------------------
-- STRIP DISTANCE -
------------------------------------------------
if spec.btnStripMinus
and mx >= spec.btnStripMinus.x and mx <= spec.btnStripMinus.x + spec.btnStripMinus.w
and my >= spec.btnStripMinus.y and my <= spec.btnStripMinus.y + spec.btnStripMinus.h then

    spec.tramCenterDistance = math.max(
        spec.tramCenterMin,
        (spec.tramCenterDistance or 2.0) - (spec.tramCenterStep or 0.1)
    )

    fahrgassenAutomatik.syncStripDistanceToServer(self)
end

------------------------------------------------
-- STRIP DISTANCE +
------------------------------------------------
if spec.btnStripPlus
and mx >= spec.btnStripPlus.x and mx <= spec.btnStripPlus.x + spec.btnStripPlus.w
and my >= spec.btnStripPlus.y and my <= spec.btnStripPlus.y + spec.btnStripPlus.h then

    spec.tramCenterDistance = math.min(
        spec.tramCenterMax,
        (spec.tramCenterDistance or 2.0) + (spec.tramCenterStep or 0.1)
    )

    fahrgassenAutomatik.syncStripDistanceToServer(self)
end

------------------------------------------------
-- HALBSEITE TOGGLE (beide → links → rechts → beide)
------------------------------------------------
if spec.btnHalfToggle
and mx >= spec.btnHalfToggle.x and mx <= spec.btnHalfToggle.x + spec.btnHalfToggle.w
and my >= spec.btnHalfToggle.y and my <= spec.btnHalfToggle.y + spec.btnHalfToggle.h then

    spec.halfSide = (spec.halfSide + 1) % 3
    fahrgassenAutomatik.applyHalfSide(self)
    if self.isServer then
        g_server:broadcastEvent(FA_SetHalfSideEvent:new(self, spec.halfSide), nil, nil, self)
    else
        fahrgassenAutomatik.syncHalfSideToServer(self)
    end
end

------------------------------------------------
-- FAHRGASSEN-TYP TOGGLE
------------------------------------------------
if spec.btnTramType
and mx >= spec.btnTramType.x and mx <= spec.btnTramType.x + spec.btnTramType.w
and my >= spec.btnTramType.y and my <= spec.btnTramType.y + spec.btnTramType.h then
    if spec.tramlineType == "cultivated" then
        spec.tramlineType = "plants"
    else
        spec.tramlineType = "cultivated"
    end
    FA_syncTramlineType(self)
end

------------------------------------------------
-- STREIFENBREITE - (Zeile 4, kein GPS)
------------------------------------------------
if spec.btnWidthMinus
and mx >= spec.btnWidthMinus.x and mx <= spec.btnWidthMinus.x + spec.btnWidthMinus.w
and my >= spec.btnWidthMinus.y and my <= spec.btnWidthMinus.y + spec.btnWidthMinus.h then
    spec.tramHalfWidth = math.max(0.1, (spec.tramHalfWidth or 0.6) - 0.05)
    if self.isServer then
        g_server:broadcastEvent(FA_SetStripWidthEvent:new(self, spec.tramHalfWidth), nil, nil, self)
    else
        fahrgassenAutomatik.syncStripWidthToServer(self)
    end
end

------------------------------------------------
-- STREIFENBREITE + (Zeile 4, kein GPS)
------------------------------------------------
if spec.btnWidthPlus
and mx >= spec.btnWidthPlus.x and mx <= spec.btnWidthPlus.x + spec.btnWidthPlus.w
and my >= spec.btnWidthPlus.y and my <= spec.btnWidthPlus.y + spec.btnWidthPlus.h then
    spec.tramHalfWidth = math.min(2.0, (spec.tramHalfWidth or 0.6) + 0.05)
    if self.isServer then
        g_server:broadcastEvent(FA_SetStripWidthEvent:new(self, spec.tramHalfWidth), nil, nil, self)
    else
        fahrgassenAutomatik.syncStripWidthToServer(self)
    end
end

------------------------------------------------
-- GPS OFFSET - (Zeile 4, GPS-Modus)
------------------------------------------------
if spec.btnGpsMinus
and mx >= spec.btnGpsMinus.x and mx <= spec.btnGpsMinus.x + spec.btnGpsMinus.w
and my >= spec.btnGpsMinus.y and my <= spec.btnGpsMinus.y + spec.btnGpsMinus.h then
    spec.gpsLateralOffset = math.max(spec.gpsOffsetMin or -10,
        (spec.gpsLateralOffset or 0) - (spec.gpsOffsetStep or 0.1))
end

------------------------------------------------
-- GPS OFFSET + (Zeile 4, GPS-Modus)
------------------------------------------------
if spec.btnGpsPlus
and mx >= spec.btnGpsPlus.x and mx <= spec.btnGpsPlus.x + spec.btnGpsPlus.w
and my >= spec.btnGpsPlus.y and my <= spec.btnGpsPlus.y + spec.btnGpsPlus.h then
    spec.gpsLateralOffset = math.min(spec.gpsOffsetMax or 10,
        (spec.gpsLateralOffset or 0) + (spec.gpsOffsetStep or 0.1))
end

------------------------------------------------
-- TOGGLE OFFSET ZEILEN
------------------------------------------------
if spec.btnToggleOffset
and mx >= spec.btnToggleOffset.x and mx <= spec.btnToggleOffset.x + spec.btnToggleOffset.w
and my >= spec.btnToggleOffset.y and my <= spec.btnToggleOffset.y + spec.btnToggleOffset.h then
    spec.showOffsetRows = not spec.showOffsetRows
end

------------------------------------------------
-- RIDGEMARKER MODUS
------------------------------------------------
if spec.btnRidgeMarker
and mx >= spec.btnRidgeMarker.x and mx <= spec.btnRidgeMarker.x + spec.btnRidgeMarker.w
and my >= spec.btnRidgeMarker.y and my <= spec.btnRidgeMarker.y + spec.btnRidgeMarker.h then
    if FA_RidgeMarker then
        local newMode = FA_RidgeMarker.getNextMode(spec.ridgeMarkerMode or "off")
        spec.ridgeMarkerMode = newMode

        -- Sofort lokal anwenden (Client wartet nicht auf Server-Roundtrip)
        if newMode == "off" then
            spec._rmForcedSide = 0
            FA_RidgeMarker.unhookActionCallback(self)
        elseif newMode == "left" then
            spec._rmForcedSide = 1
            FA_RidgeMarker.hookActionCallback(self)
        elseif newMode == "right" then
            spec._rmForcedSide = 2
            FA_RidgeMarker.hookActionCallback(self)
        elseif newMode == "both" then
            spec._rmForcedSide = 3
            FA_RidgeMarker.hookActionCallback(self)
        elseif newMode == "auto" then
            spec._rmForcedSide = (spec._rmTotalPassCount or 0) % 2 == 1 and 2 or 1
            FA_RidgeMarker.hookActionCallback(self)
        end
        spec._rmModeChanged = true

        -- Server und andere Clients synchronisieren
        FA_syncRidgeMarkerMode(self)
    end
end

------------------------------------------------
-- LÄNGSVERSATZ -
------------------------------------------------
if spec.btnForwardMinus
and mx >= spec.btnForwardMinus.x and mx <= spec.btnForwardMinus.x + spec.btnForwardMinus.w
and my >= spec.btnForwardMinus.y and my <= spec.btnForwardMinus.y + spec.btnForwardMinus.h then
    spec.tramForwardOffset = math.max(-5.0, (spec.tramForwardOffset or 0) - 0.1)
    FA_syncTramOffset(self)
end

------------------------------------------------
-- LÄNGSVERSATZ +
------------------------------------------------
if spec.btnForwardPlus
and mx >= spec.btnForwardPlus.x and mx <= spec.btnForwardPlus.x + spec.btnForwardPlus.w
and my >= spec.btnForwardPlus.y and my <= spec.btnForwardPlus.y + spec.btnForwardPlus.h then
    spec.tramForwardOffset = math.min(5.0, (spec.tramForwardOffset or 0) + 0.1)
    FA_syncTramOffset(self)
end

------------------------------------------------
-- SEITENVERSATZ -
------------------------------------------------
if spec.btnLateralMinus
and mx >= spec.btnLateralMinus.x and mx <= spec.btnLateralMinus.x + spec.btnLateralMinus.w
and my >= spec.btnLateralMinus.y and my <= spec.btnLateralMinus.y + spec.btnLateralMinus.h then
    spec.tramLateralOffset = math.max(-10.0, (spec.tramLateralOffset or 0) - 0.1)
    FA_syncTramOffset(self)
end

------------------------------------------------
-- SEITENVERSATZ +
------------------------------------------------
if spec.btnLateralPlus
and mx >= spec.btnLateralPlus.x and mx <= spec.btnLateralPlus.x + spec.btnLateralPlus.w
and my >= spec.btnLateralPlus.y and my <= spec.btnLateralPlus.y + spec.btnLateralPlus.h then
    spec.tramLateralOffset = math.min(10.0, (spec.tramLateralOffset or 0) + 0.1)
    FA_syncTramOffset(self)
end
  end
        --------------------------------------------------------------------
        --            WORKAREA GASSEN ERSTELLEN 
        --------------------------------------------------------------------
        if self.addWorkAreaGassen and self.spec_workArea then
            self.addWorkAreaGassen = nil
            fahrgassenAutomatik.createGassen(self)
        end
        ------------------------------------------------
        -- HUD DRAG
        ------------------------------------------------
        if spec.hudRect then
            local inHud = FA_pointInRect(mx, my, spec.hudRect)

            if mousePressed and inHud then
                local onButton =
                    FA_pointInRect(mx, my, spec.btnMinus)        or
                    FA_pointInRect(mx, my, spec.btnPlus)         or
                    FA_pointInRect(mx, my, spec.btnNMinus)       or
                    FA_pointInRect(mx, my, spec.btnNPlus)        or
                    FA_pointInRect(mx, my, spec.btnStripMinus)   or
                    FA_pointInRect(mx, my, spec.btnStripPlus)    or
                    FA_pointInRect(mx, my, spec.btnHalfToggle)   or
                    FA_pointInRect(mx, my, spec.btnTramType)     or
                    FA_pointInRect(mx, my, spec.btnWidthMinus)   or
                    FA_pointInRect(mx, my, spec.btnWidthPlus)    or
                    FA_pointInRect(mx, my, spec.btnGpsMinus)     or
                    FA_pointInRect(mx, my, spec.btnGpsPlus)      or
                    FA_pointInRect(mx, my, spec.btnToggleOffset) or
                    FA_pointInRect(mx, my, spec.btnRidgeMarker)  or
                    FA_pointInRect(mx, my, spec.btnForwardMinus) or
                    FA_pointInRect(mx, my, spec.btnForwardPlus)  or
                    FA_pointInRect(mx, my, spec.btnLateralMinus) or
                    FA_pointInRect(mx, my, spec.btnLateralPlus)  or
                    FA_pointInRect(mx, my, spec.modeHit)         or
                    FA_pointInRect(mx, my, spec.btnClose)

                if not onButton then
                    spec.hudDragging = true
                    spec.hudDragOffX = mx - spec.hudRect.x
                    spec.hudDragOffY = my - spec.hudRect.y
                end
            end

            if mouseDown and spec.hudDragging then
                local newX = mx - spec.hudDragOffX
                local newY = my - spec.hudDragOffY
                -- Bildschirmränder: HUD darf nicht raus
                local safeX = g_safeFrameOffsetX or 0.01
                local safeY = g_safeFrameOffsetY or 0.01
                newX = math.max(safeX, math.min(1.0 - safeX - spec.hudRect.w, newX))
                newY = math.max(safeY, math.min(1.0 - safeY - spec.hudRect.h, newY))
                spec.hudPosX = newX
                spec.hudPosY = newY
            end
        end

        if not mouseDown then
            if spec.hudDragging and not self.isServer then
                -- Drag beendet → Position zum Server syncen damit sie gespeichert wird
                local connection = g_client and g_client:getServerConnection()
                if connection then
                    connection:sendEvent(FA_SetHudPosEvent:new(self, spec.hudPosX or 0, spec.hudPosY or 0))
                end
            end
            spec.hudDragging = false
        end
    end

if spec.mode == "Pause" then
    if spec.gassenActive then
        fahrgassenAutomatik.resetGassenState(self)
    end
    return
end
        --------------------------------------------------------------------
        --    GIANTS STEERING ASSIST (GPS) → VORRANG
        --------------------------------------------------------------------
        -- FA hängt meist am Gerät, SteeringAssist am Traktor → rootVehicle nehmen
        local aiVehicle = self.rootVehicle or self
        local ai = aiVehicle.spec_aiAutomaticSteering
    
  if spec.mode == "Dauer" then
      local lowered = self:getIsLowered()
  
      if lowered then
          fahrgassenAutomatik.setGassen(self, true)
      else
          fahrgassenAutomatik.resetGassenState(self)
      end
  
      return
  end
  --------------------------------------------------------------------
  -- GPS MODUS – PASS-BERECHNUNG ÜBER REFERENZPOLYLINE (positions[])
  --------------------------------------------------------------------
  if spec.mode == "GPS" and spec.gpsRefSet then
    local pos = spec.gpsRefPositions
    if type(pos) ~= "table" or #pos < 2 then return end
    if not spec.seedWidth or spec.seedWidth <= 0 then return end

    ----------------------------------------------------------------
    -- Konfiguration
    ----------------------------------------------------------------
    spec.gpsSampleDist    = spec.gpsSampleDist or 0.5

    ----------------------------------------------------------------
    -- Fahrzeugposition
    ----------------------------------------------------------------
    local px, _, pz = getWorldTranslation(self.components[1].node)

    -- nur alle X Meter prüfen
    if spec._gpsLastSampleX then
        local dx = px - spec._gpsLastSampleX
        local dz = pz - spec._gpsLastSampleZ
        if (dx*dx + dz*dz) < (spec.gpsSampleDist * spec.gpsSampleDist) then
            return
        end
    end
    spec._gpsLastSampleX = px
    spec._gpsLastSampleZ = pz

    ----------------------------------------------------------------
    -- Helper: positions[] → (x,z)
    ----------------------------------------------------------------
    local function getXZ(p)
        if p.x and p.z then return p.x, p.z end
        if p[1] and p[2] then return p[1], p[2] end
        return nil
    end

    ----------------------------------------------------------------
    -- NÄCHSTES SEGMENT FINDEN + SIGNED DISTANCE
    ----------------------------------------------------------------
    local bestD2 = math.huge
    local signedDist = nil

    for i = 1, #pos - 1 do
        local ax, az = getXZ(pos[i])
        local bx, bz = getXZ(pos[i + 1])

        if ax and bx then
            local vx = bx - ax
            local vz = bz - az
            local lenSq = vx*vx + vz*vz

            if lenSq > 1e-6 then
                local wx = px - ax
                local wz = pz - az
                local t = (wx*vx + wz*vz) / lenSq
                t = math.max(0, math.min(1, t))

                local cx = ax + t * vx
                local cz = az + t * vz

                local dx = px - cx
                local dz = pz - cz
                local d2 = dx*dx + dz*dz

                if d2 < bestD2 then
                    bestD2 = d2

                    local invLen = 1 / math.sqrt(lenSq)
                    local nx = -(vz * invLen)
                    local nz =  (vx * invLen)

                    signedDist = dx * nx + dz * nz
                end
            end
        end
    end

    if signedDist == nil then return end

    ----------------------------------------------------------------
    -- OFFSET ANWENDEN (HIER WAR DEIN BUG)
    ----------------------------------------------------------------
    local effectiveDist = signedDist + spec.gpsLateralOffset

    ----------------------------------------------------------------
    -- PASS-BERECHNUNG: absoluter Abstand von der Reflinie
    ----------------------------------------------------------------
    local absDist = math.abs(effectiveDist)
    local newPass = math.floor(
        (absDist + spec.seedWidth * 0.5) / spec.seedWidth
    ) + 1

    newPass = ((newPass - 1) % spec.N) + 1

    ----------------------------------------------------------------
    -- DEBUG
    ----------------------------------------------------------------
if newPass ~= spec.pass then
    spec.pass = newPass
    fahrgassenAutomatik.syncPassToServer(self)
end

    ----------------------------------------------------------------
    -- FAHRGASSE
    ----------------------------------------------------------------
    local tramPass = math.floor(spec.N / 2) + 1
    fahrgassenAutomatik.setGassen(self, spec.pass == tramPass)
end
       
if spec.mode == "EnhancedVehicle" then
    local root = self.rootVehicle or self
    if root.vData and root.vData.track and root.vData.track.isCalculated and root.vData.opMode == 2 then
        -- originalTrackLR wird von EV nur in onDraw aktualisiert (nur wenn HUD sichtbar).
        -- Wir berechnen es selbst nach derselben EV-Formel damit es auch bei
        -- ausgeblendetem HUD funktioniert.
        local trackLR = root.vData.track.originalTrackLR or 0
        if root.vData.track.workWidth and root.vData.track.workWidth > 0
        and root.vData.track.origin then
            local o = root.vData.track.origin
            local dx = root.vData.px - o.px
            local dz = root.vData.pz - o.pz
            local dotLR = dx * -(o.originaldZ) + dz * (o.originaldX)
            trackLR = dotLR / root.vData.track.workWidth
        end
        local absLane = math.abs(trackLR)
        -- evPassStart: ab welchem Pass gestartet wird (durch Plus/Minus einstellbar)
        local startOffset = (spec.evPassStart or 1) - 1
        local newPass = math.floor(absLane + 0.5) + 1
        newPass = (((newPass - 1) + startOffset) % spec.N) + 1

        if newPass ~= spec.pass then
            spec.pass = newPass
            fahrgassenAutomatik.syncPassToServer(self)
        end

        local tramPass = math.floor(spec.N / 2) + 1
        fahrgassenAutomatik.setGassen(self, spec.pass == tramPass)
    end
end

if spec.mode == "Courseplay" then
local root = self.rootVehicle or self
local rebuild = false

local currentCourse = nil
if root and root.getFieldWorkCourse then
    currentCourse = root:getFieldWorkCourse()
end

local courseChanged = (currentCourse ~= spec._cpLastCourse)

------------------------------------------------
-- Bedingungen
------------------------------------------------
if spec.cpSegments == nil or spec.cpRefPoints == nil then
    rebuild = true
end

if courseChanged then
    rebuild = true
end

if spec.N ~= spec._cpLastN then
    rebuild = true
end

------------------------------------------------
-- REBUILD
------------------------------------------------
if rebuild then
    FA_Courseplay.cpRebuildSegmentPassMap(self)

    spec._cpLastCourse = currentCourse
    spec._cpLastN = spec.N
end

    local rowInfo = FA_Courseplay.getCourseplayRowInfo(self)

    -- Headland bleibt über CP-Headland-Ring
    if rowInfo ~= nil then
    end
    if rowInfo ~= nil and (rowInfo.isHeadland or rowInfo.isConnecting or rowInfo.headlandPass ~= nil or rowInfo.row == nil) then
        if spec.cpHeadlandCount == nil or spec.cpHeadlandCount <= 0 then
            spec.cpHeadlandCount = FA_Courseplay.getCourseplayHeadlandCount(self)
        end

        local ring = rowInfo.headlandPass
        if ring ~= nil then
            local newPass = ((ring - 1) % spec.N) + 1

            if newPass ~= spec.pass then
                spec.pass = newPass
                fahrgassenAutomatik.syncPassToServer(self)
            end
        end

        local tramPass = math.floor(spec.N / 2) + 1
        fahrgassenAutomatik.setGassen(self, spec.pass == tramPass)
        return
    end

    -- Feld nur noch über Segment -> absDist -> laneIndex -> pass
    local newPass, bestSeg = FA_cpGetCurrentPassFromRef(self)

    if newPass ~= nil and newPass ~= spec.pass then
        spec.pass = newPass
        fahrgassenAutomatik.syncPassToServer(self)

        if bestSeg ~= nil then
        end
    end

    local tramPass = math.floor(spec.N / 2) + 1
    fahrgassenAutomatik.setGassen(self, spec.pass == tramPass)
    return
end	


  end
  
  
  function fahrgassenAutomatik:onDraw(isActiveForInput, isActiveForInputIgnoreSelection)
  end

  function fahrgassenAutomatik.drawHUD(self)
    if not self.isClient then return end
    local spec = self[specName]
    if spec == nil or not spec.ready then return end
    if spec.hudVisible == false then return end

    -- HUD-Referenzgroessen von InputHelp holen (fuer konsistente Schrift/Abstande)
    local ih = g_currentMission and g_currentMission.hud and g_currentMission.hud.inputHelp
    if not ih or not ih.comboBg or not ih.lineBg then return end

    local comboBg   = ih.comboBg
    local lineBg    = ih.lineBg
    local textSize  = ih.textSize * 1.4
    local lineSpacing = lineBg.height * 0.95
    local totalLines  = spec.showOffsetRows and 10 or 6
    local totalH      = lineSpacing * totalLines + lineBg.height * 0.8
    local totalW      = comboBg.width

    -- Startposition: unten mittig (nur beim allerersten Draw)
    if spec.hudPosX == nil then
        spec.hudPosX = 0.5 - totalW * 0.5
        spec.hudPosY = (g_safeFrameOffsetY or 0.02) + lineBg.height * 0.5
    end

    local posX = spec.hudPosX
    local posY = spec.hudPosY

    -- Titelbalken-Hoehe: eine Zeilenhöhe
    local titleH = lineBg.height * 0.9

    -- Hintergrund (beginnt unterhalb des Titelbalkens)
    if spec.hudBgOverlay then
        spec.hudBgOverlay:renderCustom(posX, posY, totalW, totalH)
    end

    -- Titelbalken oben drüber
    if spec.hudTitleOverlay then
        spec.hudTitleOverlay:renderCustom(posX, posY + totalH, totalW, titleH)
    end

    -- Titel-Text linksbündig
    local titleTextSize = ih.textSize * 1.2
    local titleTextY    = posY + totalH + titleH * 0.19
    setTextBold(true)
    setTextAlignment(RenderText.ALIGN_LEFT)
    setTextColor(1, 1, 1, 1)
    renderText(posX + totalW * 0.02, titleTextY, titleTextSize, FA_l10n("fa_ATS"))
    setTextBold(false)

    -- X-Button rechts im Titelbalken
    local xSize  = titleH * 0.75
    local xPad   = totalW * 0.01
    local xBtnX  = posX + totalW - xPad - xSize
    local xBtnY  = posY + totalH + (titleH - xSize) * 0.7
    setTextAlignment(RenderText.ALIGN_CENTER)
    setTextColor(1, 1, 1, 1)
    setTextBold(true)
    renderText(xBtnX + xSize * 0.5, xBtnY + xSize * 0.19, titleTextSize, "x")
    setTextBold(false)
    -- Klickbereich merken
    spec.btnClose = { x = xBtnX, y = xBtnY, w = xSize, h = xSize }

    -- Hektar-Anzeige im Titelbalken: links vom X, rechts vom Titel
    local haS = (spec.areaSession or 0) / 10000
    local haT = (spec.areaTotal   or 0) / 10000
    setTextAlignment(RenderText.ALIGN_RIGHT)
    setTextBold(false)
    setTextColor(1, 1, 1, 0.9)
    renderText(xBtnX - xPad * 3, titleTextY, titleTextSize * 1.0,
        string.format("%.2f / %.2f ha", haS, haT))

    -- Toggle-Button für Versatz-Zeilen (Zahnrad-Icon links neben ha-Zähler)
    local haText = string.format("%.2f / %.2f ha", haS, haT)
    local haW = getTextWidth(titleTextSize * 1.02, haText)
    local toggleBtnX = xBtnX - xPad * 5 - haW - xSize * 0.8
    local toggleBtnS = xSize
     local toggleBtnW = xSize * 0.8   -- Breite
     local toggleBtnH = xSize * 1.3   -- Höhe
    if not spec.zahnradOverlay then
        local modDir = fahrgassenAutomatik.modDir or ""
        spec.zahnradOverlay = Overlay.new(modDir .. "img/Zahnrad.dds", toggleBtnX, xBtnY, toggleBtnS, toggleBtnS)
    end
    if spec.zahnradOverlay then
        spec.zahnradOverlay:setDimension(toggleBtnW, toggleBtnH)
        spec.zahnradOverlay:setPosition(toggleBtnX, xBtnY - xSize * 0.3)
        spec.zahnradOverlay:render()
    end
    spec.btnToggleOffset = { x = toggleBtnX, y = xBtnY - xSize * 0.3, w = toggleBtnS, h = toggleBtnS }

    ------------------------------------------------
    -- TEXT
    ------------------------------------------------
    setTextAlignment(RenderText.ALIGN_LEFT)
    setTextColor(1, 1, 1, 1)

    local tx = posX + totalW * 0.06
    local ty = posY + totalH - lineSpacing

    local distance = string.format("%.1f m", (spec.seedWidth or 0) * (spec.N or 2))
    local passDisp = (spec.pass ~= nil and spec.pass > 0) and spec.pass or 1
    local rhythm   = string.format("%d/%d", passDisp, spec.passMax or (spec.N or 2))

    local modeKey = {
        Pause           = "fa_mode_pause",
        Dauer           = "fa_mode_dauer",
        GPS             = "fa_mode_gps",
        Courseplay      = "fa_mode_courseplay",
        Manuel          = "fa_mode_manual",
        EnhancedVehicle = "fa_mode_ev",
    }
    local modeText  = spec.mode or "Pause"
    local modeLabel = FA_l10n("fa_mode") .. ": " .. FA_l10n(modeKey[modeText] or modeText)

    if spec.mode == "GPS" then
        local gpsRefStatus = spec.gpsRefSet
            and FA_l10n("fa_gps_ref_set")
            or  FA_l10n("fa_gps_ref_not_set")
        modeLabel = modeLabel .. " | " .. FA_l10n("fa_gps_ref") .. ": " .. gpsRefStatus
    end

    -- Zeile 1: Fahrgassenabstand
    renderText(tx, ty, textSize, FA_l10n("fa_distance") .. ": " .. distance)
    local yDistance = ty
    ty = ty - lineSpacing

    -- Zeile 2: Rhythmus
    renderText(tx, ty, textSize, FA_l10n("fa_rhythm") .. ": " .. rhythm)
    local yRhythm = ty

    ty = ty - lineSpacing

    -- Zeile 3: Modus (klickbar)
    renderText(tx, ty, textSize, modeLabel)
    spec.modeHit = { x = tx, y = ty, w = getTextWidth(textSize, modeLabel), h = textSize }
    local yMode = ty
    ty = ty - lineSpacing

    -- Zeile 4: Streifenbreite (kein GPS) ODER GPS-Offset (GPS-Modus)
    local yLine4 = ty
    if spec.mode == "GPS" then
        renderText(tx, ty, textSize,
            string.format("%s: %+.2f m", FA_l10n("fa_gpsOffset"), spec.gpsLateralOffset or 0))
    else
        renderText(tx, ty, textSize,
            string.format("%s: %.2f", FA_l10n("fa_stripWidth"), spec.tramHalfWidth or 0.6))
    end
    ty = ty - lineSpacing

    -- Zeile 5: Streifenabstand
    renderText(tx, ty, textSize,
        string.format("%s: %.2f m", FA_l10n("fa_stripDistance"), spec.tramCenterDistance or 0))
    local yStrip = ty
    ty = ty - lineSpacing

    -- Zeile 6: Halbseiten-Abschaltung Sämaschine (klickbar)
    local hsLabel = FA_l10n("fa_halfSide") .. ": "
    setTextColor(1, 1, 1, 1)
    renderText(tx, ty, textSize, hsLabel)
    local labelW = getTextWidth(textSize, hsLabel)

    -- Zwei horizontale Balken nebeneinander (breit, flach)
    -- grün = aktiv, rot = abgeschaltet
    local hs = spec.halfSide or 0
    local leftActive  = (hs == 0 or hs == 1)
    local rightActive = (hs == 0 or hs == 2)

    local barW = totalW * 0.3
    local barH = textSize * 0.3
    local gap  = totalW * 0.012
    local barY = ty + textSize * 0.1
    local barLX = tx + labelW
    local barRX = barLX + barW + gap

    if leftActive then
        drawFilledRect(barLX, barY, barW, barH, 0.22323, 0.40724, 0.00368, 1.0)
    else
        drawFilledRect(barLX, barY, barW, barH, 1.0, 0, 0, 1.0)
    end

    if rightActive then
        drawFilledRect(barRX, barY, barW, barH, 0.22323, 0.40724, 0.00368, 1.0)
    else
        drawFilledRect(barRX, barY, barW, barH, 1.0, 0, 0, 1.0)
    end

    local totalClickW = labelW + barW + gap + barW
    spec.btnHalfToggle = { x = tx, y = ty - textSize * 0.1, w = totalClickW, h = textSize * 1.2 }
    spec.btnHalfL = spec.btnHalfToggle
    spec.btnHalfR = nil
    local yHalf = ty

    -- Zeilen 7+8: Versatz-Einstellungen (nur wenn showOffsetRows)
    local yForward, yLateral = nil, nil
    if spec.showOffsetRows then
        ty = ty - lineSpacing
        renderText(tx, ty, textSize,
            string.format("%s: %+.2f m", FA_l10n("fa_forwardOffset"), spec.tramForwardOffset or 0))
        yForward = ty

        ty = ty - lineSpacing
        renderText(tx, ty, textSize,
            string.format("%s: %+.2f m", FA_l10n("fa_lateralOffset"), spec.tramLateralOffset or 0))
        yLateral = ty

        -- Zeile 9: RidgeMarker-Modus (klickbar)
        if FA_RidgeMarker and FA_RidgeMarker.hasRidgeMarker(self) then
            ty = ty - lineSpacing
            local rmLabel = FA_l10n("fa_rm_label") .. ": " .. FA_RidgeMarker.getModeLabel(spec.ridgeMarkerMode or "off")
            setTextColor(1, 1, 1, 1)
            renderText(tx, ty, textSize, rmLabel)
            setTextColor(1, 1, 1, 1)
            spec.btnRidgeMarker = { x = tx, y = ty - textSize*0.1, w = getTextWidth(textSize, rmLabel), h = textSize * 1.2 }
        else
            spec.btnRidgeMarker = nil
        end

        -- Zeile 10: Fahrgassen-Typ (klickbar) - nur in Einstellungen sichtbar
        ty = ty - lineSpacing
        local ttTypeText
        if spec.tramlineType == "plants" then
            ttTypeText = (g_i18n and g_i18n:hasText("fa_tramPlants")) and g_i18n:getText("fa_tramPlants") 
        else
            ttTypeText = (g_i18n and g_i18n:hasText("fa_tramCultivated")) and g_i18n:getText("fa_tramCultivated") 
        end
        local ttLabelPrefix = (g_i18n and g_i18n:hasText("fa_tramType")) and g_i18n:getText("fa_tramType")
        local ttLabel = ttLabelPrefix .. ": " .. ttTypeText
        setTextColor(1, 1, 1, 1)
        renderText(tx, ty, textSize, ttLabel)
        spec.btnTramType = { x = tx, y = ty - textSize*0.1, w = getTextWidth(textSize, ttLabel), h = textSize * 1.2 }
    else
        spec.btnTramType = nil
    end

    ------------------------------------------------
    -- +/- BUTTONS
    ------------------------------------------------
    setTextAlignment(RenderText.ALIGN_CENTER)

    local centerX = posX + totalW * 0.88
    local bSpacing = totalW * 0.08
    local bxMinus  = centerX - bSpacing
    local bxPlus   = centerX + bSpacing
    local bw = textSize * 1.2
    local bh = textSize * 1.2

    renderText(bxMinus, yRhythm,   textSize, "-")
    renderText(bxPlus,  yRhythm,   textSize, "+")
    spec.btnMinus = { x = bxMinus - bw*0.5, y = yRhythm   - bh*0.5, w = bw, h = bh }
    spec.btnPlus  = { x = bxPlus  - bw*0.5, y = yRhythm   - bh*0.5, w = bw, h = bh }

    -- Versatz-Buttons
    if spec.showOffsetRows and yForward then
        renderText(bxMinus, yForward, textSize, "^")
        renderText(bxPlus,  yForward + textSize * 0.12, textSize, "v")
        spec.btnForwardMinus = { x = bxMinus - bw*0.5, y = yForward - bh*0.5, w = bw, h = bh }
        spec.btnForwardPlus  = { x = bxPlus  - bw*0.5, y = yForward - bh*0.5, w = bw, h = bh }

        renderText(bxMinus, yLateral, textSize, "<")
        renderText(bxPlus,  yLateral, textSize, ">")
        spec.btnLateralMinus = { x = bxPlus- bw*0.5, y = yLateral - bh*0.5, w = bw, h = bh }
        spec.btnLateralPlus  = { x = bxMinus  - bw*0.5, y = yLateral - bh*0.5, w = bw, h = bh }
    else
        spec.btnForwardMinus = nil; spec.btnForwardPlus  = nil
        spec.btnLateralMinus = nil; spec.btnLateralPlus  = nil
    end

    renderText(bxMinus, yDistance, textSize, "-")
    renderText(bxPlus,  yDistance, textSize, "+")
    spec.btnNMinus = { x = bxMinus - bw*0.5, y = yDistance - bh*0.5, w = bw, h = bh }
    spec.btnNPlus  = { x = bxPlus  - bw*0.5, y = yDistance - bh*0.5, w = bw, h = bh }

    -- Zeile 4 Buttons: GPS-Offset oder Streifenbreite
    renderText(bxMinus, yLine4, textSize, "-")
    renderText(bxPlus,  yLine4, textSize, "+")
    if spec.mode == "GPS" then
        spec.btnGpsMinus   = { x = bxMinus - bw*0.5, y = yLine4 - bh*0.5, w = bw, h = bh }
        spec.btnGpsPlus    = { x = bxPlus  - bw*0.5, y = yLine4 - bh*0.5, w = bw, h = bh }
        spec.btnWidthMinus = nil
        spec.btnWidthPlus  = nil
    else
        spec.btnWidthMinus = { x = bxMinus - bw*0.5, y = yLine4 - bh*0.5, w = bw, h = bh }
        spec.btnWidthPlus  = { x = bxPlus  - bw*0.5, y = yLine4 - bh*0.5, w = bw, h = bh }
        spec.btnGpsMinus   = nil
        spec.btnGpsPlus    = nil
    end

    renderText(bxMinus, yStrip,    textSize, "-")
    renderText(bxPlus,  yStrip,    textSize, "+")
    spec.btnStripMinus = { x = bxMinus - bw*0.5, y = yStrip    - bh*0.5, w = bw, h = bh }
    spec.btnStripPlus  = { x = bxPlus  - bw*0.5, y = yStrip    - bh*0.5, w = bw, h = bh }

    setTextAlignment(RenderText.ALIGN_LEFT)

    -- Fahrgassen-Icon (aktiv-Anzeige)
    if spec.iconTram ~= nil and FA_isTramActive(spec, self) then
        local iconSize    = lineBg.height * 1.5
        local rightPad    = totalW * 0.01
        local iconX       = posX + totalW - rightPad - iconSize
        local iconY       = yMode - iconSize * 0.6
        renderOverlay(spec.iconTram.overlayId, iconX, iconY, iconSize, iconSize)
    end

    -- HUD-Rahmen fuer Drag-Zone merken
    spec.hudRect = { x = posX, y = posY, w = totalW, h = totalH + titleH }
end

  
  
  
  function fahrgassenAutomatik.actionIncrease(self, actionName, inputValue, callbackState, isAnalog)
      local spec = self[specName]
      spec.N = math.min(30, spec.N + 1)
      spec.passMax = spec.N
	  spec.gassenTarget = math.floor(spec.N / 2) + 1
	 fahrgassenAutomatik.syncNToServer(self)      -- ⭐ neu
  end
  
  function fahrgassenAutomatik.actionDecrease(self, actionName, inputValue, callbackState, isAnalog)
      local spec = self[specName]
      spec.N = math.max(2, spec.N - 1)
      spec.passMax = spec.N
	  spec.gassenTarget = math.floor(spec.N / 2) + 1
	  fahrgassenAutomatik.syncNToServer(self)      -- ⭐ neu
  end
  
  function fahrgassenAutomatik.actionResetArea(self, actionName, inputValue, callbackState, isAnalog)
      local spec = self[specName]
      if not spec or not spec.ready then return end
      if self.isServer then
          spec.areaSession = 0
          g_server:broadcastEvent(FA_ResetAreaEvent:new(self), nil, nil, self)
      else
          spec.areaSession = 0
          g_client:getServerConnection():sendEvent(FA_ResetAreaEvent:new(self))
      end
  end

  function fahrgassenAutomatik.actionPassPlus(self, actionName, inputValue, callbackState, isAnalog)
      local spec = self[specName]
      if not spec or not spec.ready then return end
      if spec.mode == "EnhancedVehicle" then
          spec.evPassStart = spec.evPassStart or 1
          spec.evPassStart = spec.evPassStart + 1
          if spec.evPassStart > spec.passMax then spec.evPassStart = 1 end
      elseif self.isServer then
          spec.pass = spec.pass + 1
          if spec.pass > spec.passMax then spec.pass = 1 end
          fahrgassenAutomatik.setGassen(self, spec.pass == spec.gassenTarget)
          fahrgassenAutomatik.onPassChanged(self)
        g_server:broadcastEvent(FA_SetPassEvent:new(self, spec.pass), nil, nil, self)
      else
          local newPass = spec.pass + 1
          if newPass > spec.passMax then newPass = 1 end
          spec.pass = newPass
          fahrgassenAutomatik.onPassChanged(self)
          g_client:getServerConnection():sendEvent(FA_SetPassEvent:new(self, newPass))
      end
  end

  function fahrgassenAutomatik.actionPassMinus(self, actionName, inputValue, callbackState, isAnalog)
      local spec = self[specName]
      if not spec or not spec.ready then return end
      if spec.mode == "EnhancedVehicle" then
          spec.evPassStart = spec.evPassStart or 1
          spec.evPassStart = spec.evPassStart - 1
          if spec.evPassStart < 1 then spec.evPassStart = spec.passMax end
      elseif self.isServer then
          spec.pass = spec.pass - 1
          if spec.pass < 1 then spec.pass = spec.passMax end
          fahrgassenAutomatik.setGassen(self, spec.pass == spec.gassenTarget)
          fahrgassenAutomatik.onPassChanged(self)
        g_server:broadcastEvent(FA_SetPassEvent:new(self, spec.pass), nil, nil, self)
      else
          local newPass = spec.pass - 1
          if newPass < 1 then newPass = spec.passMax end
          spec.pass = newPass
          fahrgassenAutomatik.onPassChanged(self)
          g_client:getServerConnection():sendEvent(FA_SetPassEvent:new(self, newPass))
      end
  end

  function fahrgassenAutomatik:triggerPass()
      local spec = self[specName]
      if not spec or not spec.ready then return end
  
      spec.pass = spec.pass + 1
  
      if spec.pass > spec.passMax then
          spec.pass = 1
          fahrgassenAutomatik.setGassen(self, true)
      else
          fahrgassenAutomatik.resetGassenState(self)
      end
  
  end
  
  function fahrgassenAutomatik.syncPassToServer(self)
    -- RidgeMarker immer aktualisieren (Server und Client)
    fahrgassenAutomatik.onPassChanged(self)

    if self.isServer then return end
    local spec = self[specName]
    if not spec then return end

    local connection = g_client and g_client:getServerConnection()
    if connection then
        connection:sendEvent(FA_SetPassEvent:new(self, spec.pass))
    end
end

-- Nach Pass-Wechsel aufrufen: RidgeMarker aktualisieren
function fahrgassenAutomatik.onPassChanged(self)
    if FA_RidgeMarker and FA_RidgeMarker.hasRidgeMarker(self) then
        FA_RidgeMarker.updateForPass(self)
    end
end
  
------------------------------------------------
-- Pflanzen entfernen ohne Bodenaenderung
------------------------------------------------
local function FA_removePlantsArea(x0, z0, x1, z1, x2, z2)
    FSDensityMapUtil.updateDestroyCommonArea(
        x0, z0,
        x1, z1,
        x2, z2,
        false, false,
        0, nil
    )
end

function fahrgassenAutomatik:onEndWorkAreaProcessing(dt, hasProcessed)
    local spec = self[specName]
    if not spec or not spec.ready then return end




    ------------------------------------------------
    -- GESÄTE FLÄCHE über wa.lastWorkedHectares
    -- GIANTS schreibt die tatsächlich bearbeiteten Hektar pro Frame direkt in die WA
    ------------------------------------------------
    if self.isServer then
        local frameHa = 0
        if self.spec_sowingMachine and self.spec_workArea and self.spec_workArea.workAreas then
            for _, wa in ipairs(self.spec_workArea.workAreas) do
                if not skipWorkAreas[wa.functionName]
                and wa.workAreaType == WorkAreaType.SOWING_MACHINE
                and wa.lastWorkedHectares ~= nil
                and wa.lastWorkedHectares > 0 then
                    frameHa = frameHa + wa.lastWorkedHectares
                end
            end
        end

        if frameHa > 0 then
            -- areaTotal/Session sind in m² gespeichert → ha × 10000
            spec.areaTotal   = (spec.areaTotal   or 0) + frameHa * 10000
            spec.areaSession = (spec.areaSession or 0) + frameHa * 10000
            g_server:broadcastEvent(FA_SyncAreaEvent:new(self, spec.areaTotal, spec.areaSession))
        end
    end

    ------------------------------------------------
    -- ⭐ ROBUSTER SERVER CHECK (Implement + Traktor)
    ------------------------------------------------
   if not self.isServer then
    return
end
-- ⭐ Reset-Cooldown aktiv → NICHT schreiben
if spec.faResetTimer and spec.faResetTimer > 0 then
    return
end
-- ⭐ Safety: wenn nicht Dauer und nicht im Zielpass → sofort raus
if spec.mode ~= "Dauer" and spec.mode ~= "Manuel" and spec.mode ~= "GPS" and spec.mode ~= "Courseplay" and spec.mode ~= "EnhancedVehicle" then
    -- Im Pause-Modus nur weiter wenn forcePass aktiv
    if spec.mode ~= "Pause" or not spec.forcePass then
        return
    end
end
------------------------------------------------
-- ⭐ ROBUSTER LOWERED + WORK CHECK
------------------------------------------------
local lowered = false

if self.getIsImplementChainLowered then
    lowered = self:getIsImplementChainLowered()
elseif self.getIsLowered then
    lowered = self:getIsLowered()
end

local speed = self:getLastSpeed() or 0

-- Muss unten UND in Bewegung sein
if not lowered or speed < 0.5 then
    return
end

    ------------------------------------------------
    -- Nur im Fahrgassen-Pass
    ------------------------------------------------
    if spec.mode ~= "Dauer"
        and not spec.forcePass
        and (spec.pass ~= spec.gassenTarget) then
        return
    end

    ------------------------------------------------
    -- WorkAreas holen
    ------------------------------------------------
    local function FA_isWorkAreaValid(wa)
        if skipWorkAreas[wa.functionName] then return false end
        if wa.workAreaType == WorkAreaType.AUXILIARY then return false end
        return wa.workAreaType == WorkAreaType.SOWING_MACHINE
    end

    local waSpec = self.spec_workArea
    if not waSpec or not waSpec.workAreas then return end

    local sowAreas = {}
    for _,wa in ipairs(waSpec.workAreas) do
        if FA_isWorkAreaValid(wa) then
            table.insert(sowAreas, wa)
        end
    end
    if #sowAreas == 0 then return end

    -----------------------------------------------------------
    -- MULTI-WA → Breitenmitte
    -- WICHTIG: Original-Nodes verwenden (nicht die verschobenen für halfSide)
    -----------------------------------------------------------
    local frontX, frontZ = 0, 0
    local backX, backZ = 0, 0
    local waCount = 0

    -- Bevorzugt: gespeicherte Originalpositionen aus sowAreaData
    if spec.sowAreaData and #spec.sowAreaData > 0 then
        for _, d in ipairs(spec.sowAreaData) do
            -- Originalpositionen in Weltkoordinaten umrechnen (lokal→Welt via Node-Parent)
            local sx, sy, sz = getWorldTranslation(d.startNode)
            local wx, wy, wz = getWorldTranslation(d.widthNode)
            local hx, hy, hz = getWorldTranslation(d.heightNode)

            local parentNode = getParent(d.startNode)
            local owsx, owsy, owsz = localToWorld(parentNode, d.origSX, d.origSY, d.origSZ)
            local owwx, owwy, owwz = localToWorld(parentNode, d.origWX, d.origWY, d.origWZ)
            local owhx, owhy, owhz = localToWorld(parentNode, d.origHX, d.origHY, d.origHZ)

            frontX = frontX + (owsx + owwx) * 0.5
            frontZ = frontZ + (owsz + owwz) * 0.5

            local vx = owwx + (owhx - owsx)
            local vz = owwz + (owhz - owsz)

            backX = backX + (owhx + vx) * 0.5
            backZ = backZ + (owhz + vz) * 0.5
            waCount = waCount + 1
        end
    else
        -- Fallback: direkt aus WA-Nodes (kein halfSide aktiv oder keine sowAreaData)
        for _,wa in ipairs(sowAreas) do
            local sx,_,sz = getWorldTranslation(wa.start)
            local wx,_,wz = getWorldTranslation(wa.width)
            local hx,_,hz = getWorldTranslation(wa.height)

            frontX = frontX + (sx + wx) * 0.5
            frontZ = frontZ + (sz + wz) * 0.5

            local vx = wx + (hx - sx)
            local vz = wz + (hz - sz)

            backX = backX + (hx + vx) * 0.5
            backZ = backZ + (hz + vz) * 0.5
            waCount = waCount + 1
        end
    end

    if waCount == 0 then return end

    frontX = frontX / waCount
    frontZ = frontZ / waCount
    backX  = backX  / waCount
    backZ  = backZ  / waCount

    -----------------------------------------------------------
    -- Richtung
    -----------------------------------------------------------
    local dx = backX - frontX
    local dz = backZ - frontZ
    local len = math.sqrt(dx*dx + dz*dz)
    if len < 0.01 then return end
    dx = dx / len
    dz = dz / len

    local sideX = -dz
    local sideZ =  dx

    -----------------------------------------------------------
    -- Offsets
    -----------------------------------------------------------
    local hw = spec.tramHalfWidth or 0.4
    local centerDist = spec.tramCenterDistance or 2.0
    local side = math.max(0, (centerDist - hw) * 0.5)

    -- ⭐ DS-stabile Länge
    local width = 0.8

    -----------------------------------------------------------
    -- Referenzpunkt: aiBackNode wenn vorhanden, sonst backX/backZ
    -----------------------------------------------------------
    local bx, bz
    if spec.aiBackNode then
        local bnx, _, bnz = getWorldTranslation(spec.aiBackNode)
        -- 0.5m nach hinten verschieben damit Fahrgasse nicht in WA ragt
        bx = bnx + dx * 1.0
        bz = bnz + dz * 1.0
    else
        local extraBack = spec.offsetBack or 0.5
        bx = backX + dx * extraBack
        bz = backZ + dz * extraBack
    end

    -- Seitlicher Versatz (z.B. für SeedingBarLeft)
    local lateralOffset = spec.tramLateralOffset or 0
    if lateralOffset ~= 0 then
        bx = bx + sideX * lateralOffset
        bz = bz + sideZ * lateralOffset
    end

    -- Längsversatz vor/zurück (positiv = nach vorne, negativ = nach hinten)
    local forwardOffset = spec.tramForwardOffset or 0
    if forwardOffset ~= 0 then
        bx = bx + dx * forwardOffset
        bz = bz + dz * forwardOffset
    end

    ------------------------------------------------
    -- ⭐ MOVEMENT SAMPLING (JETZT RICHTIG!)
    ------------------------------------------------
    spec.faWriteDist = spec.faWriteDist or 0.35

    local wxs, wzs = bx, bz

    if spec._faLastWriteX then
        local ddx = wxs - spec._faLastWriteX
        local ddz = wzs - spec._faLastWriteZ
        if (ddx*ddx + ddz*ddz) < (spec.faWriteDist * spec.faWriteDist) then
            return
        end
    end

    spec._faLastWriteX = wxs
    spec._faLastWriteZ = wzs

    -----------------------------------------------------------
    -- LINKER STREIFEN
    -----------------------------------------------------------
    local cx = bx + sideX * side
    local cz = bz + sideZ * side

    local lx0 = cx - dx * width
    local lz0 = cz - dz * width
    local lx1 = cx
    local lz1 = cz

    if spec.tramlineType == "plants" then
        -- Nur Pflanzen zerstoeren, Boden bleibt unveraendert
        FA_removePlantsArea(
            lx0, lz0,
            lx1, lz1,
            lx1 + sideX * hw, lz1 + sideZ * hw
        )
    else
        -- Standard: Boden auf gegrubbert setzen (entfernt auch Pflanzen)
        FSDensityMapUtil.updateCultivatorArea(
            lx0, lz0,
            lx1, lz1,
            lx1 + sideX * hw, lz1 + sideZ * hw,
            false,
            false,
            0,
            nil
        )
    end

    -----------------------------------------------------------
    -- RECHTER STREIFEN
    -----------------------------------------------------------
    local cxR = bx - sideX * side
    local czR = bz - sideZ * side

    local rx0 = cxR - dx * width
    local rz0 = czR - dz * width
    local rx1 = cxR
    local rz1 = czR

    if spec.tramlineType == "plants" then
        FA_removePlantsArea(
            rx0, rz0,
            rx1, rz1,
            rx1 - sideX * hw, rz1 - sideZ * hw
        )
    else
        FSDensityMapUtil.updateCultivatorArea(
            rx0, rz0,
            rx1, rz1,
            rx1 - sideX * hw, rz1 - sideZ * hw,
            false,
            false,
            0,
            nil
        )
    end
end

function fahrgassenAutomatik.actionToggleMode(self)
    local spec = self[specName]
    if not spec then
        return
    end

    spec.mode = FA_getNextMode(self, spec.mode)
    spec.pass = 0
    spec.gpsRefSet = false
    spec.gpsRefPositions = nil
	spec.cpHeadlandCount = 0
    fahrgassenAutomatik.resetGassenState(self)
    fahrgassenAutomatik.syncPassToServer(self)
    fahrgassenAutomatik.syncModeToServer(self)
end
  
function fahrgassenAutomatik.actionSetGpsRef(self)
    local spec = self[specName]
    if not spec then return end

    local root = self.rootVehicle or self
	
	local ai = (self.rootVehicle or self).spec_aiAutomaticSteering
    local course = ai and ai.steeringFieldCourse
    if not course or not course.currentSegmentIndex then
        return
    end

    local idx = course.currentSegmentIndex
    local seg = course.segments and course.segments[idx]
    if not seg or not seg.positions or #seg.positions < 2 then
        return
    end

    spec.gpsRefPositions = seg.positions
    spec.gpsRefSet = true
    spec.pass = 1

    -- Zum Server syncen damit die Linie gespeichert werden kann
    local posStr = FA_positionsToString(spec.gpsRefPositions)
    if self.isServer then
        g_server:broadcastEvent(FA_SetGpsRefEvent:new(self, posStr), nil, nil, self)
    else
        local connection = g_client and g_client:getServerConnection()
        if connection then
            connection:sendEvent(FA_SetGpsRefEvent:new(self, posStr))
        end
    end

    spec._gpsLastSampleX = nil
    spec._gpsLastSampleZ = nil

end

function fahrgassenAutomatik.actionStripWider(self, actionName, inputValue, callbackState, isAnalog)
    local spec = self[specName]
    spec.tramCenterDistance = math.min(spec.tramCenterMax, (spec.tramCenterDistance or 2.0) + (spec.tramCenterStep or 0.1))
	fahrgassenAutomatik.syncStripDistanceToServer(self)
end

function fahrgassenAutomatik.actionStripNarrower(self, actionName, inputValue, callbackState, isAnalog)
    local spec = self[specName]
    spec.tramCenterDistance = math.max(spec.tramCenterMin, (spec.tramCenterDistance or 2.0) - (spec.tramCenterStep or 0.1))
  fahrgassenAutomatik.syncStripDistanceToServer(self)
end

function fahrgassenAutomatik.syncGassenToServer(self, state)
    local connection = g_client and g_client:getServerConnection()
    if connection then
        connection:sendEvent(FA_SetGassenEvent:new(self, state))
    end
end

function fahrgassenAutomatik.syncModeToServer(self)
    if self.isServer then return end

    local spec = self[specName]
    local connection = g_client and g_client:getServerConnection()

    if connection and spec then
        connection:sendEvent(FA_SetModeEvent:new(self, spec.mode))
    end
end

function fahrgassenAutomatik.resetGassenState(self)
    local spec = self[specName]
    if not spec then return end

    -- ⭐ CLIENT → Server informieren
    if not self.isServer then
        fahrgassenAutomatik.syncGassenToServer(self, false)
    end

    -- ⭐ Server setzt wirklich zurück
    if self.isServer then
        for _,wa in ipairs(spec.workAreas) do
            wa.disabled = true
        end
    end

  spec.gassenActive = false
spec._faLastWriteX = nil
spec._faLastWriteZ = nil
spec._lastLowerState = nil

-- ⭐ NEU: kurze Schreibsperre
spec.faResetTimer = 500   -- ms
end

----------------------------------------------------
-- saveToXMLFile
----------------------------------------------------
function fahrgassenAutomatik:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self[specName]
    if not spec then return end

    -- Wenn die Saemaschine eingeklappt ist, ridgeMarkerState auf 1
    -- (eingefahren) setzen damit GIANTS den eingefahrenen Zustand
    -- speichert. Sonst startet der Marker beim Laden in der zuletzt
    -- aktiven Position (z.B. ausgefahren rechts).
    local isFolded = false
    local foldTime = self.spec_foldable and self.spec_foldable.foldAnimTime
    if foldTime ~= nil and foldTime > 0.5 then
        isFolded = true
    end
    if self.getIsUnfolded and not self:getIsUnfolded() then
        isFolded = true
    end

    if isFolded then
        local rmSpec = self.spec_ridgeMarker
        local safe = rmSpec and rmSpec.ridgeMarkers
        if safe then
            for _, rm in ipairs(rmSpec.ridgeMarkers) do
                if rm.liftedAnimTime == nil then safe = false break end
            end
        end
        if safe then
            rmSpec.ridgeMarkerState = 1
        end
    end

    key = key .. ".spec_fahrgassenAutomatik"

    xmlFile:setInt(   key .. "#N",                  spec.N or 2)
    xmlFile:setFloat( key .. "#areaTotal",          spec.areaTotal   or 0)
    xmlFile:setFloat( key .. "#areaSession",        spec.areaSession or 0)
    xmlFile:setInt(   key .. "#evPassStart",        spec.evPassStart or 1)
    xmlFile:setInt(   key .. "#pass",               spec.pass or 1)
    xmlFile:setString(key .. "#mode",               spec.mode or "Pause")
    xmlFile:setFloat( key .. "#tramCenterDistance", spec.tramCenterDistance or 2.0)
    xmlFile:setFloat( key .. "#tramHalfWidth",      spec.tramHalfWidth      or 0.6)
    xmlFile:setFloat( key .. "#tramForwardOffset",  spec.tramForwardOffset  or 0)
    xmlFile:setFloat( key .. "#tramLateralOffset",  spec.tramLateralOffset  or 0)
    xmlFile:setString(key .. "#tramlineType",       spec.tramlineType       or "cultivated")
    xmlFile:setString(key .. "#ridgeMarkerMode",    spec.ridgeMarkerMode    or "auto")
    xmlFile:setInt(   key .. "#rmTotalPassCount",   spec._rmTotalPassCount  or 0)
    if spec.hudPosX and spec.hudPosX > 0 then
        xmlFile:setFloat( key .. "#hudPosX", spec.hudPosX)
    end
    if spec.hudPosY and spec.hudPosY > 0 then
        xmlFile:setFloat( key .. "#hudPosY", spec.hudPosY)
    end
    xmlFile:setBool(  key .. "#hudVisible",         spec.hudVisible ~= false)
    xmlFile:setInt(   key .. "#halfSide",           spec.halfSide or 0)
    if spec.gpsRefSet and spec.gpsRefPositions and #spec.gpsRefPositions >= 2 then
        local str = FA_positionsToString(spec.gpsRefPositions)
        xmlFile:setString(key .. ".gpsRefLine#data", str)
    end
end

  ----------------------------------------------------
  -- Aufräumen wenn Spieler das Fahrzeug verlässt
  ----------------------------------------------------
  function fahrgassenAutomatik:onDeactivate()
      local spec = self[specName]
      if not spec then return end
      if not self.isClient then return end

      if spec.cursorActive then
          g_inputBinding:setShowMouseCursor(false)
          spec.cursorActive = false
          spec.hudDragging = false

          local root = self.rootVehicle or self
          FA_setCameraRotation(root, true, spec._faSavedRotatableInfo)

          if spec._faDriveEventsDisabled then
              spec._faDriveEventsDisabled = false
          end
      end
  end

  function fahrgassenAutomatik:onDelete()
      local spec = self[specName]
      if spec ~= nil then
          -- Zahnrad-Overlay aufräumen
          if spec.zahnradOverlay then
              spec.zahnradOverlay:delete()
              spec.zahnradOverlay = nil
          end
          if spec.hudBgOverlay ~= nil then
              spec.hudBgOverlay:delete()
              spec.hudBgOverlay = nil
          end
          if spec.hudTitleOverlay ~= nil then
              spec.hudTitleOverlay:delete()
              spec.hudTitleOverlay = nil
          end
          if spec.hudBackground ~= nil then
              spec.hudBackground:delete()
              spec.hudBackground = nil
          end
      end
  end
----------------------------------------------------
-- Globaler Mod-Listener: draw() wird immer aufgerufen
-- unabhängig davon welches Fahrzeug gerade aktiv ist
----------------------------------------------------
FA_HudListener = {}

function FA_HudListener:draw()
    if not g_currentMission or not g_localPlayer then return end
    local currentVehicle = g_localPlayer:getCurrentVehicle()
    if not currentVehicle then return end

    -- root = Traktor der Kombination
    local root = currentVehicle.rootVehicle or currentVehicle

    local function checkVehicle(v)
        local spec = v and v[specName]
        if spec and spec.ready then
            fahrgassenAutomatik.drawHUD(v)
        end
    end

    -- Traktor selbst prüfen
    checkVehicle(root)

    -- Alle angehängten Geräte rekursiv prüfen
    local function checkImplements(v)
        if not v then return end
        local implements = v.getAttachedImplements and v:getAttachedImplements()
        if not implements then return end
        for _, impl in ipairs(implements) do
            if impl.object then
                checkVehicle(impl.object)
                checkImplements(impl.object)
            end
        end
    end
    checkImplements(root)
end

addModEventListener(FA_HudListener)
