DEV Community

Cover image for Reading a locked Pet Index: verifying 199 Roblox pets without unlocking them
ZF032726
ZF032726

Posted on Fully Autonomous

Reading a locked Pet Index: verifying 199 Roblox pets without unlocking them

Steal An Egg is a Roblox game where you steal eggs, hatch pets, and the pets earn money every second. How much a pet earns depends on its base income, its weight and its mutations, so players keep asking the same question: what does this pet make?

I built a small calculator site to answer that. The code was the easy part. Most of my time went into a different question: which numbers can I actually trust?

A note on process: AI agents did a lot of the legwork here, reading the in-game index, logging slots, writing the build and the tests, while I set the rules and checked the results. The verification approach is the part I think is worth sharing.

The problem with the numbers I found

When I started, the sources disagreed with each other, and some numbers didn't come from anywhere I could trace:

  • The community wiki keeps pet data in two Lua data modules, and they didn't always agree. One listed Light Dove at $225/s, the other at $225K/s. For 18 pets from limited eggs, the "rarity" field held the egg's group name ("Monster", "Brainrot") instead of an actual rarity.
  • Guides published in August and September listed the Golden mutation as ×2. The wiki said ×2.5.
  • One existing calculator shipped preset pets whose names and incomes don't match any slot in the game's own Pet Index. A pet earning $9/s, for example, would need a slot with a $900 reward, and there isn't one.
  • Nobody had published how multiple mutations stack. One tool simply adds them together, and I couldn't find a source for that either way.

None of this is surprising for a game that updates every few weeks. But a calculator that multiplies an invented input by a correct formula still gives you a confident, wrong number. I wanted every value on the site to say where it came from.

Two sources, one rule

I used two sources:

  1. The Steal An Egg Wiki on Fandom (CC BY-SA). Its data modules give names, rarities, base incomes and base weights for 198 pets, plus 8 names for the newest biome. Every page on the site credits the wiki in the footer.
  2. The game itself. On 2026-10-09 I went through the in-game Pet Index on a fresh account, logged all 184 slots to a CSV (112 in the World tab, 72 in Limited) and kept screenshots.

When they conflict, the order is: in-game reading > official page > wiki > newer third-party source. The CSVs are frozen in the repo, and the build script only ever reads them.

The trick: locked slots still show their reward

On a fresh account the Pet Index is almost all "???". I had unlocked exactly one pet, a Chicken. But every locked slot still shows three things: its rarity, the cash reward for discovering it, and a speed reward.

The Chicken earns $1/s and its slot rewards $100. So the hypothesis was simple: discovery reward = 100 × base income.

If that holds, I don't need to unlock anything. For each slot there should be exactly one wiki pet in the same tab, with the same rarity, whose income × 100 equals the reward. If the rule were wrong, the slots wouldn't line up.

There was one wrinkle. The game abbreviates big numbers to one decimal and truncates instead of rounding, so a $18.5K/s pet shows a reward of "$1.8M", not "$1.9M". The site uses the same formatter as the game (simplified here):

var UNITS = ['', 'K', 'M', 'B', 'T', 'Qa', 'Qi', 'Sx', 'Sp', 'Oc', 'No', 'Dc'];

// In-game abbreviation: one decimal, truncated not rounded ($1.75B -> $1.7B)
function formatCompact(n) {
  if (n < 1000) return String(Math.round(n * 100) / 100);
  var i = 0;
  while (n >= 1000 && i < UNITS.length - 1) { n = n / 1000; i++; }
  var t = Math.floor(n * 10 + 1e-9) / 10;
  return t.toFixed(1).replace(/\.0$/, '') + UNITS[i];
}
Enter fullscreen mode Exit fullscreen mode

The result: outside the newest biome, every slot was claimed by exactly one pet. In the World tab, 102 of 104 slots matched exactly, and the other 2 only differed because of truncation. In the Limited tab, 71 of 72 matched exactly, plus one more truncation case.

The same pass also fixed things:

  • 19 rarities. The 18 limited-egg pets where the wiki used the group name have real rarities in the game (Tung Tung Sahur is Rare, Crawler is Cosmic), and Octophant is Divine, not Eternal.
  • Light Dove and Flame Sprite. The slot reward is $22.5M, so the income is $225K/s, not $225/s.
  • 22 wiki pets have no slot at all: event pets, fusion results and a few others. That fits with them coming from outside the normal eggs, so the site labels them as not in the index instead of guessing.

The limits, honestly:

  • This matches values, not names. The Angels & Demons tab has 8 pairs of pets with identical income, so I can confirm both pets of a pair exist, but not which slot is which.
  • The newest biome has names on the wiki but no incomes, so 7 of its 8 pets can't be matched yet. They're left out of the calculator.

One Golden Chicken settled ×2 vs ×2.5

The index gives base incomes, but mutations needed a live reading. The pet panel doesn't show weight, but the label floating above a pet in your base does. My only pet was a Golden Chicken, and its label said 61 kg, $9/s.

The wiki's size formula (for weight ratios up to 125):

ratio  = weight / baseWeight      // 61 / 8 = 7.625
size   = ratio ^ (37/60)          // ≈ 3.50
income = base × size × mutation
Enter fullscreen mode Exit fullscreen mode
  • With Golden ×2.5: 1 × 3.50 × 2.5 = 8.75. The game pays whole dollars, so it shows $9. Match.
  • With Golden ×2: 1 × 3.50 × 2 = 7.00, which would show $7. No match.

Weight is displayed as a whole number, so I re-ran it for 60.5 to 61.5 kg. Same conclusion. The cash counter agreed too: over about 1,095 seconds my money went up by $9,856, and over another 745 seconds by $6,711. Both work out to $9.00 per second, not $8.75, which is why I think payouts are rounded to whole dollars.

It's one pet at one weight, so I'm careful about what it proves. It rules out ×2 (that would need a base weight around 5 kg instead of 8), and it's consistent with the wiki's formula and the 8 kg base weight. It says nothing about how two mutations stack. I still don't know that, so when you pick more than one, the calculator shows both the added and the multiplied result instead of choosing for you.

That reading is now a pair of unit tests:

test('Golden Chicken: $1/s base, 8 kg base, 61 kg, Golden x2.5 -> 8.75, shown as $9', () => {
  const r = SAE.compute({ base: 1, weight: 61, baseWeight: 8, variant: 2.5, mutations: [], count: 1 });
  near(r.perSec.multiply, 8.75, 0.005, 'per second');
  assert.equal(SAE.gameDisplayPerSec(r.perSec.multiply), '$9');
});

test('Golden x2 would NOT match the in-game $9/s reading', () => {
  const r = SAE.compute({ base: 1, weight: 61, baseWeight: 8, variant: 2, mutations: [], count: 1 });
  assert.equal(SAE.gameDisplayPerSec(r.perSec.multiply), '$7');
});
Enter fullscreen mode Exit fullscreen mode

Another test runs the size formula against three weight and income readings for a different pet that a games site published. All three land within $0.1M.

The build: static HTML, no framework

The site is plain HTML, CSS and vanilla JS. Everything, from the build scripts and browser code to the checks and tests, is about 2,800 lines of JavaScript. No framework, no CDN, and no client-side rendering of the data.

  • data/ holds the frozen CSVs. build/ reads them and writes dist/: the pages, a pets.json for the calculators, the sitemap and robots.txt.
  • Every value table is rendered into the HTML at build time. A small post-processing step adds data-label="<column name>" to every <td>, so on narrow screens CSS can turn each row into a labelled card instead of a table that scrolls sideways.
  • Each pet gets a label from its check status: Verified in-game, Estimated (income derived from the index reward) or Unverified. The label logic lives in the build, and a test makes sure nothing still marked as pending can show up as verified.

Tests and the overflow check

npm test runs 18 node:test cases: the Golden Chicken reading, the size formula on both sides of the 125× breakpoint, the truncating formatter, mutation stacking, input validation (it must never produce NaN), and checks on the built output such as "no external scripts" and "every table cell has a data-label".

npm run check adds two more:

  • An SEO check: title, description, H1, canonical URL, and no dead internal links.
  • A layout check. It starts a clean headless Chrome (fresh profile, no extensions), talks to it over the DevTools Protocol using the WebSocket built into Node 22, so no Puppeteer, and opens all 10 pages at 360, 400 and 768 px. It fails if the page is wider than the viewport and lists the elements sticking out. It also fails on any console error or warning, uncaught exception or failed request.

The measurement itself is tiny:

const w = window.innerWidth;
const sw = Math.max(document.documentElement.scrollWidth, document.body.scrollWidth);
// sw > w means horizontal overflow: report the elements whose right edge is past w
Enter fullscreen mode Exit fullscreen mode

Tables with five or six columns are the usual way a page like this breaks on a phone, so I made overflow a hard failure instead of something to eyeball.

Deploy

Cloudflare Pages runs npm run build and serves dist/. The first build took about two minutes, and the site was live on its own domain about ten minutes after I pushed.

Result

Of the 199 pets with a known base income, 176 match a slot in the in-game Pet Index by rarity and reward. 22 have no slot, and 1 is matched by rarity only and still marked as pending. It's all on Steal An Egg Calculator, where each pet shows whether its numbers were checked in-game or come from the wiki.

What I'd do differently

  • Read the game first. I compared third-party sources against each other before I opened the Pet Index. About 40 minutes in the game settled most of those disagreements.
  • Turn every reading into a test. A screenshot is easy to forget. A failing test isn't.
  • Write down what you can't verify. "Stacking unverified, here are both readings" is more useful than a confident guess.

If you've done something similar for another game, working out the numbers from what the UI gives away, I'd like to hear how you approached it.

Top comments (0)