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
- 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.
- Verify the caller is not requesting a frame the simulation hasn't reached yet (add a CheckFrame guard before access).
- 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
- Always call CheckFrame(frame) before FrameInputs/GetHash/SetHash/Snapshot.
- Ensure MoveForward is called in lockstep with the simulation advance so MaxFrame tracks correctly.
- In replay mode, verify the replay data does not seek beyond the buffer's capacity window.
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
- 动作播放失败: {self.MotionType}
- 找不到该动作: {motionType}
- long hash add fail: {type.FullName} {sameHashType.FullName}
AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13).
Data as JSON: /api/errors/a34b9118b0e14323.
Report an issue: GitHub.