Summary of "SMB3 Roulette & Card Matching Games Explained"
SMB3 Roulette & N‑Spade Card Matching (technical analysis)
Overview
- The video (Retro Game Mechanics Explained) reverse-engineers Super Mario Bros. 3 minigames to show how the game’s RNG and buggy code produce the observed behavior: the roulette is effectively rigged/random, while the N‑Spade card matching game is far less random than it appears.
- Focus is on the console’s RNG implementation (a 15‑bit LFSR), how the roulette segments use RNG‑derived “rig delays,” and how coding oversights collapse the card shuffle space to only eight arrangements.
RNG (LFSR) implementation in SMB3
- SMB3 uses a 15‑bit linear feedback shift register (LFSR). Taps are taken from two bits (the 7th and 15th from the left) and XOR’d to produce the new bit shifted in.
- The LFSR state is stored across multiple bytes; the game keeps nine bytes (72 bits) in memory so different sprites/slots can read different bytes in the same frame.
- Keeping many bytes as cached LFSR outputs creates strong correlation between different random-number outputs — they are just shifted versions of each other. The usual solution would be to shift the LFSR as many times as needed instead of caching many bytes.
- A separate routine shifts the LFSR every frame even if no RNG value is needed; but during the roulette minigame only the first two bytes get shifted each frame due to a duplicated-but-truncated routine.
Roulette minigame mechanics and why it’s “rigged”
- Each roulette wheel has three independent segments; each segment can be in one of three states:
- 0 = spinning
- 1 = “rigged delay” (a fixed wait before slowing)
- 2 = slowing down (then stops)
- When you press to stop a segment, the code transitions it 0 → 1, reads an RNG byte, performs bitwise operations, and stores that value as the segment’s rig delay (frames to wait before it begins slowing).
Bitwise delay formulas:
- Segment 1:
delay = (RNG & 0x0F) | 0x20→ range 32..47 frames - Segment 2:
delay = (RNG & 0x1F) | 0x20→ range 32..63 frames - Segment 3:
delay = (RNG & 0x3F) | 0x40→ range 64..127 frames
Practical effects:
- Segment 1: variance 16 frames → small uncertainty; the player has a ~3‑frame guaranteed window to force an item.
- Segment 2: variance 32 frames → too large for reliable timing control.
- Segment 3: variance 64 frames (≈ one full revolution) → essentially pure chance; player control is negligible.
- Additional control rules: later segments check previous segment state so you can’t mash to immediately stop all segments.
Implementation quirk/bug that worsens this:
- The code reads three different bytes of the LFSR for segments 1–3 (bytes 2–4), but no LFSR shift occurs between button presses during the roulette itself. Because the game calls a truncated shift routine while idling in the roulette (only shifts the first two bytes), bytes 3 and 4 are fixed at the moment the cutscene ends. This means delays for segments 2 and 3 are effectively pre-determined before you press anything; only the first segment’s delay is truly fetched at press time. Combined with large delay ranges, this makes the roulette nearly impossible to reliably win.
N‑Spade (card matching) mechanics and why there are only eight arrangements
- Card set (18 cards): 4 mushrooms, 4 fire flowers, 4 stars, 2 1‑ups, 2 10‑coin, 2 20‑coin.
- Theoretical distinct arrangements (accounting for duplicates) ≈ 57.9 billion; naive 18! is ~6.4e15 but duplicates reduce it.
Actual shuffle algorithm (per game):
- The algorithm does three “shuffles” per game. Each shuffle consists of:
- Rotate step: rotate the first 15 cards by some count (the last 3 cards remain untouched).
- Triple-swap step: swap a triplet of cards separated by 4 positions (e.g., positions 1, 6, 11), potentially repeated for different triplets.
Two major programming oversights that collapse the shuffle space:
- Triple-swap loop bug: the X register that should select different triplets is always set to 0, so only one triplet (positions 1, 6, 11) is swapped and it only ever runs once. This collapses the triple-swap phase from 3^5 (243) possibilities down to 1.
- RNG return value bug: the Randomize routine shifts the LFSR and leaves the accumulator A holding only the single bit that was shifted in (value 0 or 2). Because the shuffle reads that result as the random counter (AND 31), the rotate counter Y only ever becomes 0 or 2. In practice the rotate step therefore runs only once or three times per shuffle (two possible outcomes).
Combined effect:
- With the triple-swap effectively fixed and each rotate having only 2 outcomes across 3 shuffles, the total number of possible card layouts after the whole process is 2^3 = 8. The video enumerates all eight resulting card arrangements.
- Why this happened: missing load after the LFSR routine (so the code never reads the full random byte) and a hardcoded X value for the triple-swap loop.
Suggested/cited fixes (and effect on outcome counts)
- Minimal fix: actually load the full random byte after the LFSR routine → increases shuffle outcomes to 3,375 for rotates (video states 3,375 for rotates and 243 for swaps giving 3,645 per shuffle and 3,645^3 ≈ 48.4 billion before accounting duplicates). Note: the video’s arithmetic differs in places, but the point stands that outcomes jump dramatically.
- Other commentary from the video:
- Fix load-only: increases to 3,375 outcomes (per video: 3,375 → total 3,375^3 = 38+ billion; video’s arithmetic differs but the point stands).
- Use RNG to determine number of triple swaps (not just which): would raise possibilities to ~216,000.
- If last three cards were included and all 18 rotated/swapped properly, possibilities could approach a few million (video cites ~3 million, ignoring duplicates).
General takeaway: small coding oversights (missing loads, hardcoded registers, truncated shift routine) drastically reduce effective randomness.
Practical takeaways for players and developers
- Players:
- Roulette: the third segment is effectively random; expect limited player influence. Segment 1 offers the most reliable window for skillful timing.
- N‑Spade: if you know the eight possible layouts, you can reliably clear the game (one mistake allowed).
- Developers:
- Beware of partial/cached LFSR outputs; these can create strong correlations between “independent” RNG reads.
- Ensure random routines return a full byte and that the caller actually loads and uses that byte.
- Avoid hardcoded loop indices (e.g., forcing X = 0) that collapse intended variability.
- Don’t use truncated shift routines that only update part of the RNG state in contexts where full updates are expected.
Sources / main speaker
- Video narrator / channel: Retro Game Mechanics Explained (the presenter who dissects SMB3 code).
- Primary technical source: Super Mario Bros. 3 game code (reverse‑engineered ROM assembly / LFSR RNG and minigame routines).
Category
Technology
Share this summary
Is the summary off?
If you think the summary is inaccurate, you can reprocess it with the latest model.
Preparing reprocess...