Unknown Is Not Zero: Modeling Evidence in a Fan-Made Game Wiki
Most game wikis I've used have the same failure mode: every cell is filled. Drop rate? A precise percentage. Pet speed? A precise number. Codes? A long list, all "working". Half of those numbers came from one video, a Discord screenshot, or a guess that got copied enough times to look like a fact.
While working on a fan guide for the Roblox game Ride A Pet, we took the opposite approach: a value is only shown if we can say where it came from and when. That sounds like an editorial rule, but it turned out to be mostly a data-modeling problem. Here's what that looks like in practice and what we'd do the same way again.
1. Give every fact an evidence status, not just a value
The eggs reference doesn't have one "source" column. Each row carries a label for how we know it:
- Recorded screen: the name is visible in gameplay footage we can rewatch (e.g. an Aurora Egg growing at a ranch nest).
- Reported: a dated third-party source says it (e.g. Blackhole and Cherub Eggs being searched for around the Giant Tree and a waterfall cave).
- Official artwork: the name appears in promotional art (Flame Egg, Dragon Egg). That proves the name exists, not that it's buyable today.
The UI consequence is small but important: a "Reported" row reads differently from a "Recorded" row, and a reader can see why the page is hedging.
A sketch of the shape:
type Evidence =
| { kind: "recorded"; source: string; timestamp: string } // e.g. "SpaceQT", "2:57"
| { kind: "reported"; source: string; checkedOn: string } // ISO date
| { kind: "official-art"; source: string }
| { kind: "unknown" };
interface Fact<T> {
value: T | null; // null when unknown, never 0
unit?: string; // "$/s", "stock", ...
evidence: Evidence;
}
2. null is not 0, and the UI should say so
The pets page has a line I like a lot: an unmeasured stat remains unknown; it should not be entered as zero.
That's a classic data bug wearing a game costume. If a comparison tool defaults an empty field to 0, every pet nobody measured looks terrible, and readers draw conclusions from your defaults. So:
- Missing values render as "Unknown", not as
0or-. - A number is always shown with its unit. The game shows a lightning label, a clover label, and a dollar-per-second label next to pets; a big number alone doesn't tell you whether it's movement, luck, or income.
- Comparisons only happen between the same field on two pets.
The same idea shows up in the game-pass table. Four passes (Instant Hatch, Infinite Radar, Infinite Backpack, Auto Collect) have a known purpose from published guides, but the exact rules and prices aren't confirmed. Instead of guessing, the table has a column literally called "function detail still needed for a purchase decision". Writing down what you don't know is more useful than a confident row that's wrong.
3. Snapshots get a date and a source timestamp
Shop data in a live game is volatile. A recorded Gears shop frame shows Royal Radar marked Legendary at $20K with x3 stock, and Magic Radar out of stock. That's useful for recognizing the row, and useless as a price list a week later.
So every snapshot keeps:
- the creator and video timestamp it came from (SpaceQT at 3:07, ItzVexo at 5:45),
- a note that "$" is the game's own price display, not US dollars,
- and a page-level "Checked" date plus a separate "last substantive edit" date.
Two dates sound redundant until you need them. "Checked" means "we re-read the sources on this day." "Edited" means "the content actually changed." Mixing them hides whether a page is stale or just stable.
4. The best page on the site is an empty list
Codes pages are where game wikis lose the most trust. The fan guide's answer is a page whose main content is: no verified codes, with a timestamp (24 September 2026, 03:07 UTC), the official page that was checked, and two dated third-party reports that also found none.
What makes it work as a data page is that "verified" has a definition. A code record needs four fields before it's published:
- the exact string,
- the announced reward,
- a redemption control that actually exists in the current game,
- a successful redemption check.
If any one is missing, the record doesn't exist yet. A failed attempt can't tell you whether a code expired, was mistyped or never existed, so on its own it doesn't move a row anywhere. You can see the whole structure on the Ride A Pet codes checklist; it's a nice reference if you're designing any "status" page where the honest answer is often "nothing yet".
5. Separate states that look the same
Another modeling lesson from the eggs page: a world listing, a radar target, and an egg in your basket are three different states, even though a player might call all of them "my egg". The page maps each screen to the one question it answers:
| Screen | Question |
|---|---|
| Track | What is listed in the world? |
| Radar marker | Where is this search's target? |
| Egg Basket | What am I carrying? |
| Hatch screen | What stage is this egg at? |
| Index: Eggs | What have I collected? |
If you've ever debugged an order system where "in cart", "paid" and "shipped" got collapsed into one boolean, this will feel familiar.
6. Keep personal state on the client, and say so
The egg-locations page has a small run-notes checklist: an optional target name (up to 60 characters) and seven checks, from "prepare a rideable pet" to "place it in a nest and confirm hatching". It's one checklist per browser, not synced to a game account, and the page states that the component doesn't send notes to analytics.
The "report a problem" form is similarly scoped: it sends the page address and a category (outdated, unclear, video problem, missing info), and nothing else. Writing the data boundary next to the control costs one sentence and removes a whole class of "what are you collecting?" questions.
What I'd take to any reference site
- Model evidence explicitly. A value without a source is a draft.
- Default to
null, render "Unknown", keep units attached. - Store "checked" and "edited" dates separately.
- Define what "verified" means as required fields, and let the empty state be a real, dated page.
- Put the privacy boundary of a widget right next to the widget.
None of this needs a fancy stack. It's mostly refusing to let a table cell be filled just because the layout has room for it.