SideKick
Book a Consult
gamedev
roblox
game-theory
ai
architecture
security

Designing a Trust Game: Bots That Bluff and a Server That Never Trusts

August 9, 2026 · 5 min read

By Rex Okonkwo · Lead Game Engineer

Claim the Vault is a two-player trust game on Roblox. Each round, tokens fill a shared vault. Both players publicly promise how much they'll take, then privately claim a real amount. If the combined claims fit, both get paid. If they overreach together — alarm — nobody gets anything. Three rounds, biggest bank wins.

It's a compact prisoner's-dilemma-meets-ultimatum-game, and it turned out to be a great vehicle for two engineering problems worth writing about: making AI opponents that feel like people with different psychologies, and building a server that treats every client as a liar (because in multiplayer, some are).

Bots as a boss ladder, not a difficulty slider

A single "AI difficulty" number is boring. Instead there's a ladder of four opponents, each a distinct strategy wearing a personality:

Bot Play style
Penny Keeps her promise ~80% of the time. Honest, exploitable.
Sterling Con man. Betrays more at high stakes and against known overclaimers.
NYX Models your claim history, takes exactly what fits, gambles when behind.
The Auditor (boss) Fast-adapting opponent model + bluff promises.

Each bot is just two pure functions — decidePromise(ctx) and decideClaim(ctx) — fed a context object (the vault size, round number, both banks, and the history of your past claims). The personality is entirely in how those two functions read the context.

The interesting one: NYX models you

Penny is a coin flip. Sterling adds situational betrayal. But NYX is where it gets fun — it builds a model of you from your past behavior:

local function averageClaimRatio(history)
  -- What fraction of the vault does this player typically grab?
  local total = 0
  for _, entry in ipairs(history) do total += entry.claimRatio end
  return math.clamp(total / #history, 0.05, 0.95)
end

Then it plays against that model. In non-final rounds it builds trust with fair players and punishes greedy ones:

local estRatio = averageClaimRatio(ctx.history)
local estTheirClaim = math.ceil(estRatio * ctx.vault)
if estRatio <= 0.55 then
  return ctx.myPromise          -- you've been fair → I keep my promise, we both bank
else
  return ctx.vault - estTheirClaim  -- you're greedy → I take exactly the remainder
end

And in the final round it drops the pretense and does the pure math — take what fits, unless that loses the match, in which case gamble on exactly the number needed to win:

local fits        = ctx.vault - estTheirClaim
local neededToWin = (ctx.theirBank + estTheirClaim) - ctx.myBank + 1
return math.max(fits, neededToWin)   -- cooperate if it wins; else swing for it

That single max() is the whole personality of a cold, rational endgame opponent: it will cooperate right up until cooperation guarantees a loss, then it defects with exactly the aggression required — no more, no less. Players describe NYX as "reading their mind." It's reading a running average and doing arithmetic.

The design lesson: varied, memorable AI opponents don't need varied technology. Four bots, same two-function interface, one shared context object. The difference between "honest goofball" and "calculating villain" is a handful of lines of conditional logic. Build the interface once; express personality as strategy.

The server trusts no one

The other half is less charming and more important: money safety in a game where lying is the mechanic. In multiplayer, you cannot trust the client — not because players are malicious by default, but because some are, and the ones who are will find every gap. So the architecture is server-authoritative end to end:

  • Clients send intents, not results. A client can request "I claim 4 tokens" through a rate-limited, validated remote. It can never tell the server "I won" or "give me 500 shards." All state lives server-side; the client is a renderer with buttons.
  • Bots are player-shaped proxies, carefully. Bots are Lua tables with negative user IDs and an IsBot = true flag. Real Roblox Player instances throw when you read an unknown property, so every access is guarded by an isBot() check (typeof(x) == 'table') before touching bot-only fields. Mixing real and fake players in one code path is a sharp edge; the type guard is the guardrail.

Idempotent payments: the rule that survives contact with reality

The part every monetized game gets wrong at least once: purchase processing must be idempotent. Roblox can call your ProcessReceipt handler more than once for the same purchase — retries, disconnects, server restarts mid-transaction. If you naively grant the reward every call, a network hiccup hands a player double (or you get charged back). The rule:

  • Dedupe every receipt against a durable receipts store before granting anything.
  • Return NotProcessedYet on any uncertainty, so Roblox retries later rather than you granting on a maybe.
  • A reward is granted exactly once per receipt id, ever.

This is the same discipline as idempotency keys in a payment API or a job queue — "this operation may be delivered more than once; make running it twice harmless." A game is not exempt from it just because the currency is called shards.

What to copy

  1. Model AI opponents as strategies behind one interface. Two pure functions + a context object gives you a whole boss ladder without a whole framework.
  2. The "opponent model" that feels psychic is usually a running average + arithmetic. Track the player's behavior, estimate their next move, respond. That's it.
  3. Server-authoritative always. Clients send intents; the server owns state. Especially in a game where deception is the point.
  4. Make purchase processing idempotent with durable dedup and a fail-safe "not yet" on uncertainty. Delivered-more-than-once is the default, not the exception.

A trust game is a tiny thing, but it exercises the two hardest parts of interactive software: making a machine feel like it has a mind, and making a server that assumes everyone's lying. Get those two right and the rest is polish.

Building a multiplayer game, a bot, or a system that has to be safe under adversarial users? That's our lane.


Rex Okonkwo

Rex Okonkwo

LEAD GAME ENGINEER

Rex builds SideKick's games and simulations. Writes about game AI, real-time systems, and making machines feel like they have a mind.

Building something like this?

We design and ship AI agents and LLM integrations for real products. Tell us what you're working on.

Book a Consult