Skip to main content
Blink is a powerful feature that allows you to queue network packets and release them later, creating the appearance of lag or enabling advanced movement techniques.

Overview

The BlinkManager (features/blink/BlinkManager.kt:75) intercepts network packets and stores them in a queue instead of sending them immediately. When flushed, all queued packets are sent at once.

How It Works

Packet Queueing

1

Intercept Packets

The PacketEvent handler intercepts all packets (BlinkManager.kt:125)
2

Check Action

Fires BlinkPacketEvent to determine if the packet should be queued
3

Queue or Pass

Based on the action, the packet is either queued or allowed through
4

Store Snapshot

Queued packets are stored with origin and timestamp

Packet Queue

val packetQueue: ConcurrentLinkedQueue<PacketSnapshot> = Queues.newConcurrentLinkedQueue()
Each snapshot contains:
data class PacketSnapshot(
    val packet: Packet<*>,
    val origin: TransferOrigin,  // INCOMING or OUTGOING
    val timestamp: Long
)
The BlinkPacketEvent returns one of three actions:
enum class Action(val priority: Int) {
    FLUSH(0),    // Send all queued packets
    PASS(1),     // Let this packet through
    QUEUE(2),    // Queue this packet
}
Actions are prioritized by their value, with FLUSH taking precedence (BlinkManager.kt:289-293).

Packet Filtering

Certain packets are never queued:

Always Passed

  • Connection packets (ClientIntentionPacket, ServerboundStatusRequestPacket, ServerboundPingRequestPacket)
  • Chat messages (ServerboundChatPacket, ClientboundSystemChatPacket, ServerboundChatCommandPacket)
Implementation (BlinkManager.kt:145-154):
when (packet) {
    is ClientIntentionPacket, is ServerboundStatusRequestPacket, 
    is ServerboundPingRequestPacket -> {
        return@handler
    }
    
    is ServerboundChatPacket, is ClientboundSystemChatPacket, 
    is ServerboundChatCommandPacket -> {
        return@handler
    }
}

Auto-Flush Triggers

Some packets trigger automatic flush:
  • Teleport: ClientboundPlayerPositionPacket
  • Disconnect: ClientboundDisconnectPacket
  • Death: ClientboundSetHealthPacket with health ≤ 0
This prevents desyncs and ensures game-critical packets are processed (BlinkManager.kt:157-171).

Ignored Packets

Player hurt sounds are ignored to reduce queue spam:
is ClientboundSoundPacket if packet.sound.value() == SoundEvents.PLAYER_HURT -> {
    return@handler
}

Flushing Packets

By Origin

Flush all packets from a specific origin:
BlinkManager.flush(TransferOrigin.OUTGOING)  // Send queued outgoing packets
BlinkManager.flush(TransferOrigin.INCOMING)  // Process queued incoming packets

By Condition

Flush packets matching a condition:
BlinkManager.flush { snapshot ->
    snapshot.timestamp < someTimestamp
}

By Count

Flush a specific number of movement packets:
BlinkManager.flush(count = 10)  // Flush first 10 movement packets
Implementation (BlinkManager.kt:233-254):
fun flush(count: Int) {
    var counter = 0
    
    with(packetQueue.iterator()) {
        while (hasNext()) {
            val snapshot = next()
            val packet = snapshot.packet
            
            if (packet is ServerboundMovePlayerPacket && packet.hasPos) {
                counter += 1
            }
            
            flushSnapshot(snapshot)
            remove()
            
            if (counter >= count) {
                break
            }
        }
    }
}
Cancel blink and teleport to the first queued position:
BlinkManager.cancel()
This:
  1. Teleports player to first queued position
  2. Flushes all non-movement packets
  3. Discards movement packets
  4. Clears the queue
Implementation (BlinkManager.kt:256-268):
fun cancel() {
    positions.firstOrNull()?.let { pos ->
        player.setPos(pos)
    }
    
    for (snapshot in packetQueue) {
        when (snapshot.packet) {
            is ServerboundMovePlayerPacket -> continue
            else -> flushSnapshot(snapshot)
        }
    }
    packetQueue.clear()
}

Position Tracking

Get queued player positions:
val positions = BlinkManager.positions
This extracts positions from queued ServerboundMovePlayerPackets (BlinkManager.kt:78-82):
val positions
    get() = packetQueue
        .map { snapshot -> snapshot.packet }
        .filterIsInstance<ServerboundMovePlayerPacket> { it.hasPos }
        .map { Vec3(it.x, it.y, it.z) }

Lag Detection

val isLagging = BlinkManager.isLagging
Returns true if there are queued packets (BlinkManager.kt:84-85).

Packet Rewriting

Modify queued packets before flushing:
BlinkManager.rewrite<ServerboundMovePlayerPacket> { packet ->
    // Modify packet
    packet.x += 1.0
}
This uses inline reified generics to filter and modify packets (BlinkManager.kt:275-277).

Time-Based Flushing

Check if packets have been queued longer than a delay:
if (BlinkManager.isAboveTime(1000L)) {
    // Packets have been queued for >1 second
    BlinkManager.flush(TransferOrigin.OUTGOING)
}
Implementation (BlinkManager.kt:270-273):
fun isAboveTime(delay: Long): Boolean {
    val entryPacketTime = (packetQueue.firstOrNull()?.timestamp ?: return false)
    return System.currentTimeMillis() - entryPacketTime >= delay
}

Visual Feedback

ESP Modes

Blink includes multiple ESP modes for visualization:
  • Box - Draws a box at the blink position
  • Model - Renders a player model
  • Wireframe - Shows wireframe rendering
  • None - No visualization
Modes are configured via:
private val espMode = modes(this, "Esp", 2) {
    arrayOf(
        BlinkEspBox(it, ::getEspData),
        BlinkEspModel(it, getEspData = ::getEspData),
        BlinkEspWireframe(it, ::getEspData),
        BlinkEspNone(it),
    )
}

Line Trail

Draws a line showing the blink path:
private val lineColor by color("Line", Color4b.LIQUID_BOUNCE)

private val renderHandler = handler<WorldRenderEvent> { event ->
    val matrixStack = event.matrixStack
    if (lineColor.a > 0) {
        renderEnvironmentForWorld(matrixStack) {
            drawLineStrip(
                argb = lineColor.argb,
                positions = positions.mapToArray { Vec3f(relativeToCamera(it)) },
            )
        }
    }
}

Event Handlers

Auto-Flush Handlers

Blink automatically attempts to flush based on game events:
// Flush outgoing packets each render frame
private val flushHandler = handler<GameRenderTaskQueueEvent> {
    if (fireEvent(null, TransferOrigin.OUTGOING) == Action.FLUSH) {
        flush(TransferOrigin.OUTGOING)
    }
}

// Flush incoming packets each tick
private val flushReceiveHandler = handler<TickPacketProcessEvent> {
    if (fireEvent(null, TransferOrigin.INCOMING) == Action.FLUSH) {
        flush(TransferOrigin.INCOMING)
    }
}

World Change Handler

Clears packets on disconnect:
private val worldChangeHandler = handler<WorldChangeEvent> { event ->
    if (event.world == null) {
        packetQueue.clear()
    }
}

Use Cases

Lag Switch

Simulate lag to confuse opponents:
// Queue packets for 2 seconds
if (BlinkManager.isAboveTime(2000)) {
    BlinkManager.flush(TransferOrigin.OUTGOING)
}

Advanced Movement

Queue movement packets and release them to teleport:
// Move while blinking
// Then flush to appear to teleport
BlinkManager.flush(TransferOrigin.OUTGOING)

Desync

Create client-server position desync:
// Cancel to snap back to first position
BlinkManager.cancel()

Gradual Flush

Flush packets gradually:
// Flush 5 movement packets per tick
BlinkManager.flush(count = 5)

Best Practices

Blink automatically clears on world change. Don’t manually clear unless needed.
Don’t prevent auto-flush for teleport/death packets - this can cause severe desyncs.
Large queues can cause lag spikes when flushed. Consider periodic partial flushes.
When in doubt, use cancel() instead of flush() to prevent invalid game states.

Configuration

Blink is configured as a ValueGroup:
object BlinkManager : EventListener, ValueGroup("BlinkManager")
This allows it to be toggled and configured through the client’s module system.

Build docs developers (and LLMs) love