PRs drift toward the merge gate on their own; the only input is a one-slot holding bay. Pull the current front PR aside to let the ones behind it merge first, then release it once its area has had time to cool down — the same area merging twice inside a short window is a conflict. The bay only ever holds one, so the real skill is reading which PR is actually risky right now versus which one just happens to be first in line, because a second risky PR right behind the held one has nowhere to go.
Timed to a report making the rounds this week: 96%+ of engineering teams now use AI coding tools, but review throughput hasn’t kept pace — which is exactly this shape, a widening gap between how fast changes arrive and how carefully they can be let through.
Reuse
The mechanic lives in src/merge-queue.js as a framework-free ES module:
createMergeQueue(opts)— returns a state object; callupdate(dt)each frame andhold()on input.hold()is a single toggle: with nothing held it pulls the current frontmost PR into the bay; with one held it releases it back into the lane at whatever position it was frozen at.opts:areas(default four labels),laneSpeed,spawnIntervalBase/spawnIntervalMin/spawnDecay(spawn rate ramps up over a run),conflictWindow(how soon the same area can safely merge again),maxConflicts(0 disables the fail state),duration(0 = endless).- State exposes
prs(each{ id, area, x, held },x1 at spawn down to 0 at the gate),heldId,lastEvent({ area, conflict, at }, for a render flash at the gate),score(merged/conflicts/streak/bestStreak), andstatus('playing'|'finished', withreason'jammed'|'time-up'). Rendering and input are entirely the caller’s job.
Port notes for DragonRuby: update() and hold() have no DOM dependencies;
the lane-to-screen mapping and bay layout belong to the renderer, same split
as the demo.
Gotchas
- A PR only merges by reaching
x <= 0whileheldis false; freezing it mid-lane (rather than removing and reinserting it) means everything behind it keeps closing distance independently — it’s a real siding, not a queue-jump. conflictWindowis measured againstlastMergeAt[area], which only updates on an actual merge (clean or not) — holding a PR doesn’t touch the clock, only merging does.- Spawn interval decays geometrically toward
spawnIntervalMinevery spawn, so density (and therefore conflict pressure) ramps up over a long run even thoughlaneSpeednever changes. - The demo bundles its own copy of the module (self-contained by contract).
If you touch
src/, re-copy it intodemo/.