Milon's Secret Honeycomb

Some games like adding Easter eggs or bonuses for players who waste their time on monotonous tasks. Knowing that, some absolutely brilliant trolls like making up time-wasting tasks and promoting them as legitimate, because just the very idea of someone attempting such a task for nothing is hilarious. Both practices are prevalant enough that it can be difficult to discern real from fake, an unfortunate boon to the latter group.

One such alleged bonus is that in Milon's Secret Castle, collecting 256 coins in the fireplace room will award bonus health.

As it turns out, this is not a secret bonus. But it is true! Rather than a bonus, this is a bug that arises from an apparent safeguard and a common programming optimization that was slightly miscalculated.

Infinite money

Our first question should be why the fireplace room coins can be recollected in the first place.

Normally, coins are collectible only once. Collection is handled by the routine HandleMoneyHoneyCollection, which (as the nomenclature implies) also handles the collection of honeycombs. The end of this routine calls FlagObjectAsCollected, which manipulates the object's tilemap coordinates to flag a single bit in a table held in RAM at $0722, which we'll call TILEFLAG.

If you're wondering where room ID comes into the equation: it doesn't. Object collection is tracked only by position within the room. If that sounds like it can lead to conflicts, it can and does! There are multiple examples of coin pairs that conflict with each other, making them mutually exclusive in a given playthrough.

These two coins are at the exact same coordinates within their respective rooms.
If you collect one, you cannot collect the other.

That one's for the collection. For the reveal, we need to understand another mechanic.

Indoor screens have 32 different object types, with IDs from $00 up through $1F. The coin is object1E and the honeycomb is object1F. Objects 1C and 1D are flagged as breakable with the bubble, and they contain a coin and honeycomb, respectively. When object1C is destroyed, it transforms into object1E; likewise with objects 1D and 1F.

This transformation also requires that the object at this position not be flagged as collected in the TILEFLAG table. If its position is flagged, as determined by the CheckIfObjectIsCollected routine, the ID of the transformation tile is changed to $00, that of an empty space.

There are four other breakable blocks, with IDs $18, $19, $1A, and $1B. These are dynamic destructibles which have different transformations depending on the current room, and, importantly, they do not call CheckIfObjectIsCollected. For this discussion, we only need to look at the definition of object1B in room 08 (the fireplace room), which is $1E; this block turns into a coin. It does, in fact, become a real coin, and it still gets flagged as collected when grabbed. The only difference is that object1B doesn't bother checking if it's been previously collected; it will always transform into a coin (in the fireplace room).

Is this an oversight, or was it intentional? We can't really know for sure, but it does seem pretty intentional. It was likely done as a safeguard to prevent players from spending all their money on health refills and being unable to purchase required progression items.

The coins in room 1 are also unique. They're placed directly in the tilemap, and while they do flag collection like any other coin, they can be collected 3 times. Address $BC is a counter for how many times the door next to these coins is entered. If this value is 3 or greater, then any floating coin—one not hiding in a block—on the tilemap will be removed.

This comes with two caveats, both of which allow these coins to be collected more than the intended 3 times.

First, the counter is incremented upon entering the adjacent door, which means if you exit the room via some other means, such as dying, it won't get incremented.

Second, this is an 8-bit counter with no bounds checking, so entering the shop 256 times will overflow the counter back to 0, allowing the coins to spawn again.

With that concept fresh on our minds, let's see why collecting 256 coins results in a honeycomb.

Optimization problems

As previously discussed, a single routine handles the collection of all objects. Objects with IDs from $00 through $1D immediately exit the routine. ID $1E is treated as a coin, and everything else is a honeycomb.

The logic of this routine is in 3 segments: coins, honeycomb, and cleanup, in that order. Here's the routine, in full:

HandleMoneyHoneyCollection:
LDA.b $2B
Get tile ID
CMP.b #$1E
Is it a coin?
BCC .exit
Don't flag
BNE .honeycomb
Not a coin
.coin
LDA.b $29
Cache object coordinates
STA.b $61
LDA.b $2A
STA.b $63
JSR TilemapXYtoFullCoordinates
JSR IsAbsoluteOnScreen
Is object visible on screen?
BCS .no_smoke
LDA.b #$02
JSR SpawnSmokePuff
.no_smoke
LDA.b #$10
Play SFX10
STA.b $E6
LDA.b #$01
Add 1 coin
JSR AddCurrency
INC.w $07BD
Number of coins collected
BNE .continue
Jump over honeycomb code
.honeycomb
JSR PerformCollectionJingle
LDA.b $B3
Get Max HP
CLC
ADC.b #$08
Add 1 bar (8 health)
STA.b $B3
Save as new Max HP
STA.b $B2
Full heal
.continue
JSR FlagObjectAsCollected
LDA.b $2B
Get tile ID
PHA
Remember it
LDA.b #$00
ID for empty space
STA.b $2B
JSR ChangeObjectType
JSR RedrawObject
PLA
Recover tile ID
STA.b $2B
.exit
RTS

Our focus is the highlighted code at $9A20, which increments a counter that tracks how many coins have been collected in a room then uses the BNE instruction to jump to the cleanup portion of the code. This instruction looks at the last operation performed, and, if that resulted in any value other than $00, jumps ahead to the location specified in its operand.

Like the shop visit counter, this coin counter is an 8-bit value that will overflow at 256, resetting the counter to 0. That might make it sound like this could be an intentional secret! Can you think of any other reason why they would even check for zero here?

I can.

Space on the NES is very limited and thus valuable. The mapper used by this game only gives 32 kilobytes of code plus some swappable graphics banks. When all was said and done, Milon's Secret Castle only had 5 spare bytes (that's ignoring any unused data or bloated code). Every byte mattered, and by this point, developers had a few tricks up their sleeve.

The standard way to move from one location in code to another is with the JMP instruction. This instruction has a 1-byte opcode and a 2-byte operand—the location to jump to. This amounts to 3 bytes, which isn't bad, but developers had realized that if they knew for certain the state of the CPU, they could save 1 byte by replacing jump instructions with branch instructions.

This trick is used in a couple places in this game, such as in the umbrella collection code at $D150. It was also used in the code above—the coin collection code—at $9A23.

The developers did not intend to reward a player for collecting 256 coins; in fact, they assumed no one would bother collecting that many. That or they assumed this counter would reset often enough to never reach that high. In either case, it's almost certain that this BNE was an optimization. Their thinking was likely that, being a counter, this value will never be zero, which makes it the perfect candidate to save a byte by using a determinated flag to branch over another segment of code. As you've probably already guessed, this was not a good assumption.

Or maybe it was. We can also pin the blame on shops for not resetting the coin counter upon exit. Perhaps when this code was written, it was the case that the counter got reset. The only way to bring the coins back is to reload the room, and doing so by exiting to the overworld will also reset the coin counter. It's likely that shops don't reset this counter because it could result in a softlock were you to collect every coin then load a shop before grabbing the key.

It's also likely that the developers just never considered the implications of repeatedly reloading a room for coins. They may have even added the respawning coins as a last minute thing and didn't have time to revisit other coin-related code.

I can speculate a dozen more scenarios for why this is the way it is, but I think we've satisfyingly answered our original question.

Over all, I'm pretty confident in saying this wasn't intended.

Summary

As ridiculous as it sounds, it's possible to gain unintended health upgrades by repeatedly reloading a single room and grabbing coins until you've collected 256. Being able to get the coin counter that high is a joint consequence of two different softlock prevention measures, and granting the health on the overflow appears to be the result of a common optimization gone wrong.

Some games even make this the entire point of playing and call it "leveling up".

Music boxes don't reset the counter either when returning you to the room, but they're not repeatable, unlike shops.

Keys don't spawn until you have collected 4 coins and have done one of either 15 blocks destroyed or 5 enemies killed. Also, I think this mechanic is good evidence that the honeycomb is unintentional. The overflow would affect key spawns too (obviously; they're the only reason this counter exists). It would be confusing if you knew that keys required 4 coins, but you were unable to spawn one despite collecting 256 coins.

You may now be wondering why only the fireplace room has respawning coins or why it needs to have them if shops don't reset the counter. That's probably because the fireplace room is unique in that you can exit to the overworld via the boss without ever collecting the key. So, if they didn't respawn, you could exhaust the coins, leave to the overworld, then return and the coin counter would be zero. Of course that then raises the question of why the extra softlock prevention is needed. A locked door is the only way to exit the other puzzle rooms, but if you can exit this room via other means, does the locked door even need to exist? This is ignoring our earlier assumption that coins respawn to prevent players from running out of money. I guess these had to go somewhere, and the fireplace room was as good a choice as any; letting them replenish here serves double duty.