Forest Logo
search
package_2

action-buffer

By @zenukopublic

Roblox

Mirrored from Wally

action-buffer

High-performance circular ring-buffered action queuing and prediction reconciliation for competitive combat games.

License: MIT Language: Luau Wally: Available


Production Origin: This package was extracted and generalized from infrastructure developed for Kabbrawl, a private competitive multiplayer Roblox experience. Kabbrawl itself remains proprietary; this package contains only reusable infrastructure and no proprietary assets or game-specific content.


⚔️ Why Action Buffering?

In fast-paced fighting, action RPG, or sports games, player inputs often arrive while the character is finishing an animation, stunned, or during a frame transition.

Without an input buffer:

  • Inputs pressed 50 ms before an attack ends are dropped, making controls feel unresponsive and "stiff".
  • With a naive array queue, memory allocations occur every frame, triggering garbage collection hitches.
  • Inputs queued too early execute unintentionally after long delays.

action-buffer provides a zero-allocation circular ring buffer with a strict temporal expiration window (e.g. 150 ms) and integrated client prediction reconciliation.


✨ Features

  • Zero Memory Allocation in Hot Paths: Circular ring buffers reuse table slots without resizing or GC pressure.
  • Temporal Window Consumption: Queued inputs expire naturally if the player cannot act within the window.
  • Multi-Channel Isolation: Separate channels for Attack, Dodge, Guard, and Skill inputs.
  • Prediction Reconciliation: Match optimistic client-predicted actions against server-confirmed sequence IDs.

🚀 Installation

Via Wally

Add action-buffer to your wally.toml:

[dependencies]
ActionBuffer = "zenukopublic/action-buffer@1.0.0"

📖 Quickstart

local ActionBuffer = require(Packages.ActionBuffer)

local buffer = ActionBuffer.new({
    capacity = 8,
    windowSeconds = 0.15, -- 150 ms buffer window
})

-- When player presses attack button (even during recovery frames):
UserInputService.InputBegan:Connect(function(input)
    if input.KeyCode == Enum.KeyCode.E then
        buffer:push("Attack", currentFrame, { attackType = "Slash" })
    end
end)

-- In your 60 Hz combat tick / state machine:
RunService.Heartbeat:Connect(function()
    if characterState:canAttack() then
        local action = buffer:consume("Attack")
        if action then
            characterState:executeAttack(action.payload.attackType)
        end
    end
end)

🧪 Testing

Run standalone unit tests headlessly with Lune:

lune run tests/action_buffer.test.luau

📄 License

MIT License. Free for personal and commercial use.

Package Details

Install command (Click to copy)


Version

1.0.0

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.