egametang/ET · critical · Exception

frame out: {frame}, maxframe: {this.MaxFrame}

Error message

frame out: {frame}, maxframe: {this.MaxFrame}

What it means

Thrown by FrameBuffer.EnsureFrame when a frame index is negative or exceeds MaxFrame. FrameBuffer is a ring buffer for lockstep simulation; MaxFrame starts at (initialFrame + 30s of frames) and advances via MoveForward one frame at a time. Accessing a frame beyond this ceiling means the buffer has not been extended to cover that simulation step yet.

Source

Thrown at Packages/cn.etetet.lockstep/Scripts/Model/Share/FrameBuffer.cs:63

        {
            if (frame < 0)
            {
                return false;
            }

            if (frame > this.MaxFrame)
            {
                return false;
            }

            return true;
        }

        private void EnsureFrame(int frame)
        {
            if (!CheckFrame(frame))
            {
                throw new Exception($"frame out: {frame}, maxframe: {this.MaxFrame}");
            }
        }
        
        public OneFrameInputs FrameInputs(int frame)
        {
            EnsureFrame(frame);
            OneFrameInputs oneFrameInputs = this.frameInputs[frame % this.frameInputs.Capacity];
            return oneFrameInputs;
        }

        public void MoveForward(int frame)
        {
            if (this.MaxFrame - frame > LSConstValue.FrameCountPerSecond) // 至少留出1秒的空间
            {
                return;
            }
            
            ++this.MaxFrame;

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Ensure MoveForward is called the correct number of times before accessing frames at the simulation frontier — the server must deliver frame data that triggers MaxFrame advancement.
  2. Verify the caller is not requesting a frame the simulation hasn't reached yet (add a CheckFrame guard before access).
  3. For replays, ensure MaxFrame is set high enough or the replay logic calls MoveForward incrementally.

Example fix

// before
public OneFrameInputs FrameInputs(int frame)
{
    EnsureFrame(frame);
    return this.frameInputs[frame % this.frameInputs.Capacity];
}
// after: caller checks first
if (!buffer.CheckFrame(frame))
{
    Log.Error($"frame {frame} out of range, max {buffer.MaxFrame}");
    return null;
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard before accessing a frame in the buffer
if (!buffer.CheckFrame(frame))
{
    Log.Error($"Frame {frame} out of range, max {buffer.MaxFrame}");
    return; // or handle appropriately
}
var inputs = buffer.FrameInputs(frame);

Prevention

When it happens

Trigger: Calling FrameInputs, GetHash, SetHash, or Snapshot with a frame number greater than MaxFrame or less than 0. This happens when the lockstep simulation tries to read/write a future frame before MoveForward has been called enough times to extend the buffer, or when a desync/replay seeks to a frame the buffer doesn't cover.

Common situations: Lockstep simulation advances faster than MoveForward is called (server hasn't sent enough frame data). A replay system seeks to a frame beyond the buffer's MaxFrame. The buffer capacity (60s * frameRate) is too small for long matches and MaxFrame advancement logic has a bug. Negative frame from an underflow in frame arithmetic.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/a34b9118b0e14323. Report an issue: GitHub.