HD texture packs for old games look simple from the outside: identify a tile, replace it with a larger image, and move on. That works until the same tile is reused for several different things—which happens constantly on hardware built around tiny memory budgets.

In Super Mario Bros. 2: The Lost Levels, the same enemy graphics can appear in an overworld, underwater, underground, or castle area. A texture rule based only on the pixels being drawn cannot always tell which version belongs on screen. It may work in one level and quietly fail in another.

The useful question is not “what tile is this?”

The more reliable question is: what does the game currently believe is happening?

The game already tracks its world, stage, area type, background, foreground, and visual mood in RAM. Those values are much closer to intent than a screenshot or tile hash. Once the emulator can use them as conditions, a rule can say:

Use the underwater Goomba art
when the current area is water.

That sounds obvious. The hard part is making the condition stable enough to survive transitions, reused areas, hidden rooms, and special levels that do not follow the normal pattern.

Special cases are where the design proves itself

World 9-1 was a useful example. The level looks and behaves like an underwater area, but the ordinary area value does not identify it the same way as the rest of the water stages. A rule that handled only the common case would be almost correct, which is often worse than being obviously broken.

The fix was to combine the normal area check with a narrow override based on the loaded FDS bank and stage. That keeps the general rule readable while documenting the exception instead of hiding it in another pile of hashes.

Rendering order matters too

Replacing a sprite with a larger image creates a second problem: the emulator has to preserve the original game’s layering. A shell, wing, or added piece can flicker, drift, or appear behind the wrong object when the replacement system does not have a stable anchor.

The eventual solution was not a giant new renderer. A lightweight priority compositor and direct anchors fixed the visible problems without turning a sixty-frame-per-second game into a six-frame-per-second technical demonstration.

The broader lesson

Old software usually already contains more useful state than the screen reveals. When a visual rule becomes fragile, the answer is often to move closer to that state rather than adding more guesses on top of the output.

It is less flashy than computer vision or a total rendering rewrite. It is also easier to debug, faster to run, and much more likely to keep working after the next texture is added.

Project: The current work lives in MesenCE-Libretro on GitHub.