"""Assemble the stripped-down aim base (trident-base.lua) from the original tribe file.

The silent aim is reduced to: the hooks, one target picker that only cares about the FOV
circle, and one direction function that points straight at the aim part with the
projectile's own drop added back. No lead, no velocity tracking, no gates.
"""

src = open('/tmp/opencode/tribe.lua', encoding='utf-8').read()
L = src.split('\n')

def keep(a, b):
    return '\n'.join(L[a-1:b])

NEW_AIM_CORE = '''-- ------------------------------------------------------------------ aim

-- Every weapon the game launches as a projectile: Bow, Crossbow, Musket, Pistol,
-- Blunderbuss, Javelin, Knives, Sling, Bomb. Drawn weapons carry a charge in the
-- payload, guns send a flat ShotSpeed instead.
local function shotParams(tool)
    if not tool or not tool:IsA("Tool") then return nil end
    local weaponId = tool:GetAttribute("WeaponId")
    local definition = weaponId and Weapons.Get(weaponId)
    local tuning = definition and definition.tuning
    if not tuning then return nil end
    local kind = definition.kind or tool:GetAttribute("Kind")
    if not Combat.FiresProjectile(kind) then return nil end
    return tuning, kind, weaponId
end

local oneWayLatency
local killAuraAim

-- Where the shot is pointed: the volume the server resolves damage against, or the head
-- when that is the configured hit part.
local function aimPartFor(head, hitbox, player, dummy)
    local mode = player and CONFIG.playerHitPart or (dummy and CONFIG.dummyHitPart or "auto")
    local part = hitbox or head
    if hitbox and mode == "head" then part = head end
    return part
end

-- The one thing the aim can wait on, and only when "wait for target visible" is on: a
-- straight line from the muzzle to the aim point that the world does not block.
local function hasLine(origin, point, model)
    local toPoint = point - origin
    if toPoint.Magnitude < 0.01 then return false end
    local dir = toPoint.Unit
    local targetParams = RaycastParams.new()
    targetParams.FilterType = Enum.RaycastFilterType.Include
    targetParams.FilterDescendantsInstances = { model }
    local hit = workspace:Raycast(origin, dir * (toPoint.Magnitude + 2), targetParams)
    if not hit then return true end
    local worldParams = Util.WorldParams(Util.Teammates())
    local exclude = worldParams.FilterDescendantsInstances
    exclude[#exclude + 1] = model
    worldParams.FilterDescendantsInstances = exclude
    return workspace:Raycast(origin, dir * math.max(0.1, hit.Distance - 0.05), worldParams) == nil
end

-- Target selection: everyone inside the FOV circle, whoever sits closest to the
-- crosshair. Being in the circle is the whole requirement - no range gate, no speed gate,
-- no hold. The target already being shot at keeps a small bonus so the pick does not
-- flick between two bodies standing together, and that bonus never beats a target that is
-- clearly closer to the crosshair.
local function selectTarget(origin, prefer)
    local look = Util.GetAimRay().Direction.Unit
    local best, bestScore
    for _, model in ipairs(targetPool()) do
        local head, root, player, dummy, hitbox = validTarget(model)
        if head then
            local part = aimPartFor(head, hitbox, player, dummy)
            local delta = part.Position - origin
            local distance = delta.Magnitude
            if distance > 0.01 then
                local angle = math.deg(math.acos(math.clamp(look:Dot(delta.Unit), -1, 1)))
                if angle <= CONFIG.fov then
                    if not CONFIG.simulate or hasLine(origin, part.Position, model) then
                        local score = angle
                        if prefer == model then score = score - 3 end
                        if not bestScore or score < bestScore then
                            bestScore = score
                            best = { model = model, head = head, hitbox = hitbox, part = part,
                                angle = angle, distance = distance, root = root,
                                velocity = root.AssemblyLinearVelocity, direction = delta.Unit }
                        end
                    end
                end
            end
        end
    end
    return best
end

-- The direction that gets sent: straight at the aim point, lifted by the drop the
-- projectile takes over the flight (off with "gravity compensation"). Nothing about the
-- target's motion is predicted.
local function aimDirection(origin, point, speed, gravity, arc)
    local distance = (point - origin).Magnitude
    if distance < 0.01 then return nil end
    local lift = 0
    if CONFIG.gravityComp and (gravity or 0) > 0 and (speed or 0) > 0 then
        local flight = distance / speed
        lift = 0.5 * gravity * flight * flight * (arc == "high" and 1.8 or 1)
    end
    local delta = (point + Vector3.new(0, lift, 0)) - origin
    if delta.Magnitude < 0.01 then return nil end
    return delta.Unit
end

local function targetIsAirborne(model)
    local root = model and model:FindFirstChild("HumanoidRootPart")
    if not root then return false end
    return math.abs(root.AssemblyLinearVelocity.Y) > 8
end
'''

NEW_PLANSHOT = '''-- The shot path every hook uses: who to point at, and the direction to send.
local function planShot(origin, speed, gravity, prefer)
    local target = selectTarget(origin, prefer)
    if not target then
        STATE.selectDebug = { reason = "no target in FOV" }
        return nil
    end
    local point = target.part.Position
    local lead = CONFIG.leadScale or 0
    if lead > 0 and (speed or 0) > 0 then
        -- one pass: the flight to where they are now, and that share of their own
        -- travel carried forward
        local flight = (point - origin).Magnitude / speed
        point = point + target.velocity * flight * lead
    end
    local direction = aimDirection(origin, point, speed, gravity,
        CONFIG.mode == "mortar" and "high" or nil)
    if not direction then return nil end
    target.direction = direction
    target.aimPoint = point
    target.firstPart = target.part.Name
    return target
end
'''

NEW_MORTAR = None  # replaced by an inline api entry

pieces = [
    keep(1, 167),
    keep(169, 320),
    NEW_AIM_CORE,
    keep(834, 834),
    NEW_PLANSHOT,
    keep(976, 1021),
    keep(1036, 1042),
    keep(1052, 1061),
    keep(1100, len(L)),
]
out = '\n'.join(pieces)

def sub(old, new, count=1):
    global out
    assert out.count(old) == count, 'pattern (%d) %r' % (out.count(old), old[:100])
    out = out.replace(old, new)

def drop(old, count=1):
    sub(old, '', count)

# ---------------------------------------------------------------- config
drop("""    velocityWindow = 0.12,
""")
sub("""    leadSeconds = 0,""", """    gravityComp = true,    -- lift the shot by the drop it takes over the flight""")
drop("""    acquireDelay = 0.12,   -- require a stable line of sight before shooting
""")
drop("""    maxFlight = 0.35,      -- do not take shots that cannot realistically land
""")
drop("""    hitGate = true,        -- auto-fire only when the solve is a likely hit
""")
sub("""    instantFire = true,    -- drawn weapons: full charge with no draw wait""",
    """    leadScale = 0.2,       -- share of the target's travel over the flight to aim ahead by
    instantFire = true,    -- drawn weapons: full charge with no draw wait""")

sub("""    simulate = true,       -- integrate the shot before spending the arrow on it""",
    """    simulate = true,       -- wait for a clear line first (target visible)""")
drop("""    predictMode = "simple", -- "simple" | "off"
""")
drop("""    leadScale = 0.5,       -- half the target's predicted travel; pinned in code
""")
drop("""    delayComp = 0,         -- extra constant trim on top of the above
""")
sub("""    viewDelay = 0,         -- extra lead beyond the ping (constant time)""",
    """    viewDelay = 0,         -- trim on the incoming-shot timing used by the dodge""")

# ---------------------------------------------------------------- the lead is a setting
sub("""            if key ~= "leadScale" and not (pinned and pinned[key] ~= nil) then""",
    """            if not (pinned and pinned[key] ~= nil) then""")

sub("""        if current == nil or name == "leadScale" then""",
    """        if current == nil then""")

# ---------------------------------------------------------------- persistence clamps
drop("""    CONFIG.leadScale = 0.5
""", 2)
sub("""    CONFIG.dodgeDistanceMin = math.clamp(tonumber(CONFIG.dodgeDistanceMin) or 6, 2, 40)""",
    """    CONFIG.viewDelay = math.clamp(tonumber(CONFIG.viewDelay) or 0, 0, 0.2)
    CONFIG.leadScale = math.clamp(tonumber(CONFIG.leadScale) or 0.2, 0, 1)
    CONFIG.dodgeDistanceMin = math.clamp(tonumber(CONFIG.dodgeDistanceMin) or 6, 2, 40)""")

# ---------------------------------------------------------------- target state
sub("""local function targetState(model, root)
    local fp, fv = filteredState(model, root)
    return fp, fv, nil
end""",
    """local function targetState(model, root)
    return root.Position, root.AssemblyLinearVelocity, nil
end""")

# ---------------------------------------------------------------- projectile hook
sub("""            local stableOrigin = typeof(origin) == "Vector3" and origin or aimOrigin(tool)
            local kind = tuning.projectile or "Arrow"
            -- prefer the gravity the server was measured to apply
            gravity = measuredGravity(kind, gravity) or gravity
            STATE.aimWeaponId = tool:GetAttribute("WeaponId")
            local locked = (lock.model and lock.model.Parent) and lock.model or nil
            local solution = selectSolution(stableOrigin, speed, gravity, tuning, locked)""",
    """            local stableOrigin = typeof(origin) == "Vector3" and origin or aimOrigin(tool)
            local kind = tuning.projectile or "Arrow"
            STATE.aimWeaponId = tool:GetAttribute("WeaponId")
            local locked = (lock.model and lock.model.Parent) and lock.model or nil
            local solution = planShot(stableOrigin, speed, gravity, locked)""")

sub("""                if solution then
                    rec.model = solution.model
                    rec.flight = solution.time
                    local tRoot = solution.model:FindFirstChild("HumanoidRootPart")
                    rec.fireTargetPos = tRoot and tRoot.Position or nil
                    local myRoot = LP.Character and LP.Character:FindFirstChild("HumanoidRootPart")
                    local myHum = LP.Character and LP.Character:FindFirstChildOfClass("Humanoid")
                    rec.mySpeed = myRoot and myRoot.AssemblyLinearVelocity.Magnitude or 0
                    rec.myMoving = myHum and myHum.MoveDirection.Magnitude > 0.1 or false
                    rec.sentDir = solution.direction
                    rec.predictAt = solution.future
                    rec.sentDir = solution.direction
                    rec.sentOrigin = stableOrigin
                    rec.leadUsed = leadTime()
                    rec.assumedGravity = gravity
                    rec.predictMode = CONFIG.predictMode
                    local v = solution.velocity
                    rec.targetSpeed = v and v.Magnitude or 0
                    if rec.targetSpeed and rec.targetSpeed > 1 and v then rec.velDir = v.Unit end
                end""",
    """                if solution then
                    rec.model = solution.model
                    local tRoot = solution.model:FindFirstChild("HumanoidRootPart")
                    rec.fireTargetPos = tRoot and tRoot.Position or nil
                    local myRoot = LP.Character and LP.Character:FindFirstChild("HumanoidRootPart")
                    local myHum = LP.Character and LP.Character:FindFirstChildOfClass("Humanoid")
                    rec.mySpeed = myRoot and myRoot.AssemblyLinearVelocity.Magnitude or 0
                    rec.myMoving = myHum and myHum.MoveDirection.Magnitude > 0.1 or false
                    rec.sentDir = solution.direction
                    rec.sentOrigin = stableOrigin
                    rec.assumedGravity = gravity
                end""")

sub("""                logEvent("aim", string.format(
                    "-> %s angle=%.1f flight=%.3f speed=%.0f vsRoot=%.1f box=%s path=%s sim=%s",
                    solution.model.Name, solution.angle or 0, solution.time or 0, speed, vsRoot,
                    tostring(solution.hitbox and solution.hitbox.Name), tostring(solution.firstPart),
                    solution.simHit and string.format("hit at %.3fs", solution.simTime or 0)
                        or "not simulated"))""",
    """                logEvent("aim", string.format(
                    "-> %s angle=%.1f distance=%.0f speed=%.0f vsRoot=%.1f part=%s",
                    solution.model.Name, solution.angle or 0, solution.distance or 0, speed, vsRoot,
                    tostring(solution.firstPart)))""")

# ---------------------------------------------------------------- pellets hook
sub("""            local solution = selectSolution(origin, speed, gravity, tuning)""",
    """            local solution = planShot(origin, speed, gravity)""")

# ---------------------------------------------------------------- pellets log
sub("""                logEvent("aim", string.format("-> %s (pellet spread) angle=%.1f flight=%.3f speed=%.0f",
                    solution.model.Name, solution.angle or 0, solution.time or 0, speed))""",
    """                logEvent("aim", string.format("-> %s (pellet spread) angle=%.1f distance=%.0f speed=%.0f",
                    solution.model.Name, solution.angle or 0, solution.distance or 0, speed))""")

# ---------------------------------------------------------------- namecall redirect
sub("""                STATE.lastShot = { id = payload.id, charge = payload.charge, speed = pending.speed,
                    target = sol.model, part = sol.firstPart, velocity = sol.velocity,
                    flightTime = sol.time, residual = sol.residual, direction = payload.dir,
                    mode = CONFIG.mode, arrivalY = sol.arrivalY, lead = CONFIG.leadSeconds }""",
    """                STATE.lastShot = { id = payload.id, charge = payload.charge, speed = pending.speed,
                    target = sol.model, part = sol.firstPart, direction = payload.dir,
                    mode = CONFIG.mode, angle = sol.angle, distance = sol.distance }""")

# ---------------------------------------------------------------- lock
sub("""local function updateLock()
    if closed then return end
    local tool, tuning = toolTuning()
    if not tool or not CONFIG.enabled then
        lock.model, lock.solution = nil, nil
        return
    end
    local origin = aimOrigin(tool)
    local speed = tuning.speed or tuning.speedMax or tuning.speedMin or 250
    local gravity = measuredGravity(tuning.projectile or "Arrow", tuning.gravity or 0) or (tuning.gravity or 0)
    local keep = lock.model and lock.model.Parent and lock.model or nil
    local solution = selectSolution(origin, speed, gravity, tuning, keep)""",
    """local function updateLock()
    if closed then return end
    if not CONFIG.enabled then
        lock.model, lock.solution = nil, nil
        return
    end
    -- with no prediction the lock needs no weapon: the muzzle when one is held, the
    -- camera otherwise, so the highlight and the target hud follow the circle at all times
    local char = LP.Character
    local tool = char and char:FindFirstChildWhichIsA("Tool")
    local origin
    if tool and shotParams(tool) then
        origin = aimOrigin(tool)
    else
        origin = Camera.CFrame.Position + Camera.CFrame.LookVector * 2
    end
    local keep = lock.model and lock.model.Parent and lock.model or nil
    local solution = selectTarget(origin, keep)""")

# ---------------------------------------------------------------- autofire gates
drop("""-- The acquire gate: only shoot when the solve is a shot that lands.
local function shotClears(started)
    local held = started and (os.clock() - started) or 0
    return held >= (CONFIG.acquireDelay or 0.3)
end

""")

sub("""            local speed = tuning.speedMax or tuning.speed or 250
            local solution = selectSolution(origin, speed, tuning.gravity or 0, tuning)
            if not solution then return end
            if CONFIG.hitGate then
                local flight = solution.time or 1
                local stability = STATE.trackStability(solution.model)
                if flight > (CONFIG.maxFlight or 0.35) or stability > 2.5
                    or not shotClears(STATE.lockSince) then
                    CAL.gated = (CAL.gated or 0) + 1
                    return
                end
            end
            pressPrimary()""",
    """            local speed = tuning.speedMax or tuning.speed or 250
            if not planShot(origin, speed, tuning.gravity or 0) then return end
            pressPrimary()""")

sub("""        local speed = tuning.speedMax or tuning.speed or 250
        if not selectSolution(origin, speed, tuning.gravity or 0, tuning)
            and now < drawUntil + 0.4 then
            return
        end""",
    """        local speed = tuning.speedMax or tuning.speed or 250
        if not planShot(origin, speed, tuning.gravity or 0) and now < drawUntil + 0.4 then
            return
        end""")

sub("""    local speed = tuning.speed or tuning.speedMax or tuning.speedMin or 250
    local solution = selectSolution(origin, speed, tuning.gravity or 0, tuning)
    if not solution then return end
    if CONFIG.hitGate then
        local flight = solution.time or 1
        local stability = STATE.trackStability(solution.model)
        -- A target that has only just become visible is usually peeking past cover
        -- and will be back behind it by the time the arrow arrives, so require the
        -- same target to have been continuously shootable for a moment first.
        if flight > (CONFIG.maxFlight or 0.35) or stability > 2.5
            or not shotClears(STATE.lockSince) then
            CAL.gated = (CAL.gated or 0) + 1
            return
        end
    end
""",
    """    local speed = tuning.speed or tuning.speedMax or tuning.speedMin or 250
    if not planShot(origin, speed, tuning.gravity or 0) then return end
""")

# ---------------------------------------------------------------- unfired diagnostics
sub("""            if rec.predictAt and rec.model and rec.model.Parent then""",
    """            if false then""")

# ---------------------------------------------------------------- planMortar is gone
start = out.index('local function planMortar(model)')
stop = out.index('-- ------------------------------------------------------------------ CS:GO menu')
out = out[:start] + out[stop:]

# ---------------------------------------------------------------- unload
drop("""    heartbeat:Disconnect()
""")

# ---------------------------------------------------------------- api surface
sub("""    solve = solveIntercept,
    fitState = function(samples, window) return stateFit(samples, window) end,
    filterProbe = function(list)
        local rec = {}
        for _, m in ipairs(list) do filterUpdate(rec, m.p, m.t) end
        return rec.fp, rec.fv
    end,
""",
    """    solve = aimDirection,
    hasLine = hasLine,
    selectTarget = function(origin) return selectTarget(origin, nil) end,
""")

sub("""    minimumLaunch = minimumLaunch,
    planMortar = function(model) return planMortar(model) end,""",
    """    checkPath = hasLine,
    planMortar = function(model)
        local char = LP.Character
        local tool = char and char:FindFirstChildWhichIsA("Tool")
        local tuning = shotParams(tool)
        if not tuning then return nil, "No equipped bow" end
        local origin = Util.ShotOrigin(tool, "Muzzle")
        local target
        if model then
            local head, root, player, dummy, hitbox = validTarget(model)
            if head then
                target = { model = model, head = head, part = aimPartFor(head, hitbox, player, dummy) }
            end
        end
        target = target or selectTarget(origin)
        if not target then return nil, "No target in FOV" end
        local speed = tuning.speedMax or tuning.speed or 250
        local gravity = tuning.gravity or 60
        local point = target.part.Position
        if (CONFIG.leadScale or 0) > 0 then
            local flight = (point - origin).Magnitude / speed
            point = point + target.velocity * flight * (CONFIG.leadScale or 0)
        end
        local direction = aimDirection(origin, point, speed, gravity, "high")
        if not direction then return nil, "No direction" end
        return { target = target.model, direction = direction, speed = speed, gravity = gravity,
            charge = 1, drawSeconds = tuning.drawTime or 0,
            distance = (point - origin).Magnitude }
    end,""")

sub("""    resolve = function(charge)
        local char = LP.Character
        local tool = char and char:FindFirstChildWhichIsA("Tool")
        local tuning = shotParams(tool)
        if not tuning then return nil end
        local _, speed = Combat.DrawnShot(tuning, charge or 1)
        local origin = Util.ShotOrigin(tool, "Muzzle")
        return selectSolution(origin, speed, tuning.gravity or 60, tuning)
    end,""",
    """    resolve = function(charge)
        local char = LP.Character
        local tool = char and char:FindFirstChildWhichIsA("Tool")
        local tuning = shotParams(tool)
        if not tuning then return nil end
        local _, speed = Combat.DrawnShot(tuning, charge or 1)
        local origin = Util.ShotOrigin(tool, "Muzzle")
        return planShot(origin, speed, tuning.gravity or 60)
    end,""")

sub("""    fitVelocity = velocityFit,
""", "")

# ---------------------------------------------------------------- the challenge model helper
sub("""    local active = (lock.model and lock.solution and lock.model.Parent) and lock.model or solutionModel()""",
    """    local active = (lock.model and lock.solution and lock.model.Parent) and lock.model
        or (STATE.lastSolution and STATE.lastSolution.model)""")

# ---------------------------------------------------------------- stale comment
sub("""-- Blunderbuss: the spread pattern is generated client-side from a unit direction,""",
    """-- Blunderbuss: the spread pattern is generated client-side from a unit direction,""")

open('/tmp/opencode/trident-base.lua', 'w', encoding='utf-8').write(out)
print('base lines:', out.count('\n') + 1)
for name in ['selectSolution', 'planShot', 'selectTarget', 'aimDirection', 'hasLine', 'shotParams',
             'velocityFit', 'filterUpdate', 'targetVelocity', 'leadTime', 'TRACK', 'planMortar',
             'trackStability', 'shotClears', 'solveIntercept', 'heartbeat', 'hitGate', 'leadScale',
             'predictMode', 'maxFlight', 'acquireDelay', 'dropComp', 'gravityComp']:
    print('%-16s %d' % (name, out.count(name)))
