Devlog / Transmission
RobloxRetentionAnalyticsCase Study

Roblox Game Retention: Fixing Our First-Session Funnel

How we lifted first escape in Shopping Cart Mayhem from 24% to 76% by fixing one leak in the first session, plus the analytics events we used to find it.

7 min readTumblix Studios

Most advice about Roblox game retention starts with daily rewards, streaks and limited-time events. Those help, but they only work on players who come back. Our problem in Shopping Cart Mayhem was earlier than that: most new players never had one good run. This post is the case study of how we found the leak, what we changed, and the analytics setup that told us where to look.

Shopping Cart Mayhem is a supermarket heist game. You get a shopping list, steal the items into your cart, outrun security, and walk out through the exit to cash out. A round is short and the loop is simple, so we expected new players to get it fast. The data said otherwise.

The funnel before the fix

Roblox's Creator Dashboard has an onboarding funnel view. You log numbered steps from your server code, and the dashboard shows what share of new players reached each step. We logged four steps for the first session:

StepWhat it meansOld build
1. JoinedA new player entered the server100%
2. First HeistTheir first round started87.7%
3. First StealThey put one item in the cart76.2%
4. First EscapeThey walked out with loot and got paid24.3%

The first three steps looked healthy. Nearly nine in ten players started a heist, and three in four stole something. Then the funnel fell off a cliff: only about one in four ever escaped. Two thirds of the players who had already done the fun part (stealing) left without the payoff.

A later read on the same old build, over a longer window, showed first escape closer to 38%. But it also showed the next problem: only about 14% of new players started another heist after their first escape. So even the players who won once were not staying for a second round.

Why the funnel could not tell us why

A funnel tells you where players stop, not why. A player who stole an item and never escaped could have been caught by a guard, run out of time, got lost, or just closed the app. Each of those needs a different fix.

So we added one custom event, HeistOutcome, logged every time a heist ends for a player. It records how the heist ended, how long the player was in it, which map it was, and whether the player was a rookie (no paid escape yet) or a regular. The outcomes are:

  • escape: walked out with loot
  • empty_escape: walked out with nothing
  • bust: caught by security
  • rookie_caught: a new player was caught (more on this below)
  • end: the round timer ran out
  • left: the player quit mid-heist

With that event split by the rookie segment, the story was clear. New players were stealing, starting a chase they did not understand, and getting busted. A bust ended their trip, sent them back to the lobby, and they left.

What we changed

We made one rule for the fix: do not add new systems. Make the first run easier to win and make the second run one tap away.

A calm list for new players

A new player now gets a shopping list built only from calm items. These are items whose steal does not raise the wanted level enough to start a chase. One steal never brings the guards. The player learns "put the item in the cart" before they have to learn "lose the guards".

Send them to the exit after one item

On the old build, the tutorial wanted a full list. On the new build, after one item the tutorial and the guide arrow point straight to the exit. The first win is small, but it is a win, and it teaches the end of the loop (walk out to get paid) on the first run.

Getting caught is a setback, not game over

If a guard catches a rookie in Classic mode, the player drops their loot and goes back to the store doors. They stay in the heist. A regular player who gets caught is busted and leaves the round as before. Losing loot still stings, so getting caught still matters, but it no longer throws a new player out of the game.

A warning before every catch

Guards now wind up for 0.8 seconds before a catch, with a warning marker over their head. If you drive away in that window, you get away. Before this, a catch felt random on a phone screen. Now a player can see it coming and react, which feels fair even when they lose.

GO AGAIN

In Classic, a player who escapes or gets busted now sees a GO AGAIN button in the lobby five seconds later. One tap sends them back into the round that is still running, until the last 45 seconds. Before, a player had to wait for the whole round to finish and then sit through the next lobby vote. That wait is exactly where a new player decides to leave.

Rookie rounds stay simple

When a new player is in the lobby, the round is forced to Classic mode, and to the Mega Mart map if nobody voted for a map. The team modes and the other maps are fun once you know the loop, but they are a bad first lesson. We also cut the lobby countdown to 15 seconds, so the wait between joining and playing stays short.

The result

We shipped the fix with a new build label (more on that next) and read the funnel only for players who started on the new build. On the first 220 new players:

MetricOld buildNew build
First escape~24% (later read ~38%)75.9%
Another heist after first escape~14%51.8%

HeistOutcome for the rookie segment showed rookies escaping in 71% of their heists. Regulars, on the normal rules, were busted in about a third of theirs, which is about where we want the challenge to sit.

220 players is a small sample, and those players came mostly from ads, so we treat the exact numbers with care. But a jump from one in four to three in four is not noise. The first-session leak was real, and it was ours to fix.

How to set up the events

Here is the shape of our analytics module, cut down. Two details matter more than the rest.

local AnalyticsService = game:GetService("AnalyticsService")

local STEPS = { "Joined", "First Heist", "First Steal", "First Escape", "Heist After Escape" }
local BUILD = "first-escape-2026-10-07" -- bump when a build changes the funnel

local Analytics = {}
local funnel = {} -- [Player] = { [step] = true }, only for new players

-- Analytics must never break the game.
local function call(fn)
	local ok, err = pcall(fn)
	if not ok then
		warn("[Analytics] call failed:", err)
	end
end

function Analytics.onboarding(player, step)
	local done = funnel[player]
	if step == 1 and not done then
		done = {}
		funnel[player] = done
	end
	-- Step 5 only counts after this visit's first escape.
	if not done or done[step] or (step == 5 and not done[4]) then
		return
	end
	done[step] = true
	call(function()
		AnalyticsService:LogOnboardingFunnelStepEvent(player, step, STEPS[step], {
			[Enum.AnalyticsCustomFieldKeys.CustomField01.Name] = BUILD,
		})
	end)
end

function Analytics.heistOutcome(player, outcome, seconds, map, segment)
	call(function()
		AnalyticsService:LogCustomEvent(player, "HeistOutcome", seconds, {
			[Enum.AnalyticsCustomFieldKeys.CustomField01.Name] = outcome,
			[Enum.AnalyticsCustomFieldKeys.CustomField02.Name] = map,
			[Enum.AnalyticsCustomFieldKeys.CustomField03.Name] = segment,
		})
	end)
end

return Analytics

Put the build in a custom field

Every funnel step carries the build label in CustomField01. The dashboard can filter by custom field, so you can compare the old build and the new build side by side even while both are in the data window. Without the label, a fix that ships mid-week gets averaged with the old build, and a big win looks like a small one. Bump the label every time a build changes the first session.

Only new players, each step once

The funnel starts only for players with no saved progress, and each step logs once per player. Step 5 (another heist after the first escape) only counts after step 4 in the same visit. If you log steps for returning players too, your funnel fills up with people who already know the game and hides the leak.

Ask why with one custom event

You do not need dozens of events. One event at the moment that matters (the end of a heist, in our case), with the reason in a custom field and a segment that splits rookies from regulars, answered the question the funnel could not.

What we took away

Fix the first win before you build reasons to come back. A daily reward does nothing for a player who leaves in the first five minutes. Once first escape was healthy, we moved on to the next leak: what happens after the first win. That work (daily streaks, daily missions, upgrades) is a story for another post, and it is measured with the same build label.

If you are new to building on Roblox, start with our guide to making a Roblox game. For the design side of a strong first minute, read designing for a 5-second hook. And since most of these new players were on phones, our mobile-first design notes cover the screen-size half of the same problem. You can play Shopping Cart Mayhem and our other titles on the games page.

// Next transmissions

Keep reading