For every bidder

How the Next Player Is Picked

This is the exact rule Squady uses to decide who comes up for auction next — not a summary, the actual rule, written in plain language, and identical to what runs in the code. No admin picks favorites, and no player's turn is decided on the spot.

1. Icon players go first — no exceptions
Every player falls into one of two groups when the roster is set up: Icon (“Bidder's Choice”) players, and everyone else. As long as even one Icon player hasn't been sold or marked unsold yet, the system will only draw from that group — it never looks at a regular player until every single Icon player has had their turn.
src/app/api/auction/[id]/next-player/route.ts
// ICON PLAYERS MUST BE AUCTIONED FIRST
// Only show regular players after ALL icon players have been auctioned
const iconPlayersAvailable = availablePlayers.filter(p => p.isIcon)

if (iconPlayersAvailable.length > 0) {
  // Regular players cannot be shown until all icon players are processed
  randomPlayer = iconPlayersAvailable[Math.floor(Math.random() * iconPlayersAvailable.length)]
} else {
  const regularPlayersAvailable = availablePlayers.filter(p => !p.isIcon)
  randomPlayer = regularPlayersAvailable[Math.floor(Math.random() * regularPlayersAvailable.length)]
}
2. Within that group, it's a genuine random draw
Whichever group is currently “live” — Icon or regular — the specific player chosen is decided by the same kind of random draw a computer uses to shuffle a deck: every player still available in that group has an equal chance. There's no weighting by price, stats, team need, or anything else, and the admin running the auction doesn't choose who's next any more than the bidders do.
src/lib/offline-auction-store.ts
export function pickRandomPlayer<T extends { isIcon: boolean }>(available: T[]): T | null {
  if (available.length === 0) return null
  const iconPlayers = available.filter(p => p.isIcon)
  const pool = iconPlayers.length > 0 ? iconPlayers : available.filter(p => !p.isIcon)
  if (pool.length === 0) return null
  return pool[Math.floor(Math.random() * pool.length)]
}

This is the same function the offline backup console calls — see “It's the same rule everywhere” below.

3. Unsold players get another chance
Once every regular player has either been sold or gone unsold at least once, anyone still marked unsold is automatically put back into the draw pool — like reshuffling a discard pile back into the deck — and the random draw continues from there. A player going unsold once doesn't mean they're done for the day.
src/app/api/auction/[id]/next-player/route.ts
// If no available players, automatically recycle UNSOLD players back to AVAILABLE
// IMPORTANT: Only recycle UNSOLD players, NEVER recycle SOLD players
if (availablePlayers.length === 0) {
  const unsoldPlayers = auction.players.filter(p => p.status === 'UNSOLD')

  if (unsoldPlayers.length > 0) {
    // Convert only UNSOLD players back to AVAILABLE (never SOLD players)
    await prisma.player.updateMany({
      where: {
        id: { in: unsoldPlayers.map(p => p.id) },
        auctionId: params.id,
        status: 'UNSOLD' // Explicit status check ensures SOLD players are never updated
      },
      data: { status: 'AVAILABLE', soldTo: null, soldPrice: null }
    })
  }
}
4. It's the same rule everywhere, every time
This exact rule runs identically whether a player is being picked right after a sale, when the auction first goes live, when the admin manually skips ahead, or even if the venue loses internet and the auction has to continue from Squady's offline backup console. There's no separate, looser process for exceptions — the fallback follows the same random, Icon-first rule as the main auction, so a network outage never becomes a reason someone's turn was decided differently.
Have a question about how a specific auction ran? Ask that auction's admin — every sale, unsold call, and player order is kept in the auction's own history.