the cutting room mirror

Spelunky (2008)

This article has a talk page!This article has a talk page!
view on tcrf.net

Spelunky is a Game Maker game about exploration, treasure, and dying to spikes. The game's source code was released on December 25, 2009, allowing many to make mods and look at the unused files.

The game had a remake, also called Spelunky. To avoid confusion, this older game has been officially called Spelunky Classic and the remake has gained the unofficial community name Spelunky HD.

To do:

  • The Mossmouth fourms acted as the hub for Classic and apparently is where versions earlier then 1.1 were uploaded. The forums are down as of writing this, so older versions are impossible to currently recover because of this.
    • Version 0.98.1 has been found hosted here and here. It is missing features like Kali Altars, but has some different graphics, the Lush music is extended, Craps Houses are used instead of Dice Houses, flares had an inventory system that were replaced by flare crates in later versions, Cape and Jetpack could be worn at the same time, and more.
  • There's more debug functions in oGame, but they are commented out in the source code. oPlayer1 also has commented debug functions for pressing ← Backspace and ↵ Return. These should go on a Development article.
  • There may be unused level data in the level generation scripts.

Debug Content#

Level Skip#

oPlayer1's Create Event code defines firstLevelSkip and levelSkip as 1. These variables are used by the game to increment the level number, but firstLevelSkip is only used in level 1, so that variable can be edited to allow skipping levels while preserving normal level progression after skipping levels. In the source code, firstLevelSkip has the comment "for debugging" before it.

oGame Debug Functions#

The Draw Event for oGame has several debug functions to draw text on the screen. In the source code, there is the comment "DEBUGGING TEXT" marking the beginning of these debug functions.

Display Level Path#

SpelunkyClassic_Debug_Level_Path.png

This code draws a grid of 4×4 numbers to the right of the player. Each number corresponds to a room in the level (a group of 10×8 tiles), with the number representing what type of room was generated. The code for this uses if (false and room == rLevel), so it never runs because of the false. The rLevel check means that it only runs in levels with the 4×4-style room layout, meaning that it doesn't run in rushing water levels, Ice Caves (levels 9 through 12), or the final boss level (level 16). In the Spelunky Classic source code, this function has the comment "Display level path" before it.

Here is what all possible level path numbers are. These numbers are used internally to determine what type of room to generate.

#

Description

0

Side, Idol, and Kali Altar rooms.

1

Path. Normal, entrance, and exit rooms with open sides.

2

Path. Rooms where the player is expected to drop into the room below.
This number is also used for rooms where the player drops in from the room above and drops into the room below.
Entrance can get assigned this, forcing the player to go to the room below.

3

Path. Rooms where the player is expected to drop in from the room above.
Exit can get assigned this, forcing the player to drop in from the room above.

4

Shop with the entrance on the right side.

5

Shop with the entrance on the left side.

6

Moai in Ice Caves. Lady Xoc (golden statue) in City of Gold (Temple).

7

Snake pit top. Rushing water lake bottom. Psychic presence ship left. Prayers to Kali top.

8

Snake pit middle. Rushing water lake surface. Psychic presence ship middle. Prayers to Kali middle.

9

Snake pit bottom. Rushing water lake Megamouth. Psychic presence ship right. Prayers to Kali bottom.

stats.txt CHESTS OPENED Text#

This code reads line 6 of stats.txt and displays the text from it above the player. The following line is what is displayed, with # being the number from stats.txt:

CHESTS OPENED:      #

Because stats.txt is only written to when the game closes, the number does not update in real time. The code for this uses if (false and instance_exists(oPlayer1)), so it never runs because of the false.

Display Stun Timer#

This code displays the stun timer in frames above and to the right of the player. The code for this uses if (false and instance_exists(oPlayer1)), so it never runs because of the false.

Distance to Black Market#

This code displays the x distance, then y distance to the Black Market entrance above the player if it exists in the level and is active (almost all objects become inactive and freeze when too far offscreen). The text displayed from this function is yellow. The code for this uses if (false and room == rLevel and instance_exists(oXMarket)), so it never runs because of the false.

Mystery of the Moon Room#

A few unused objects in Spelunky Classic combined with filenames and variable names from the source code reveals that the Moon Room, an archery challenge, was originally a basketball challenge!

Internal Names#

In the source code, the variable for the Moon Room score is named baskets. Additionally, the HUD icon of the target used in the Moon Room has the name sHoopsIcon. Both of these names do not make sense in the context of an archery challenge, but they do make sense if used for a basketball challenge.

Unused Objects for the Moon Room#

Basket#

Three different basketball hoop objects exist: oBasket, oRim, and oRimDeflect. The player passes through all of these objects.

oBasket

Sheet

Animation

sBasketSwoosh

  • oBasket is a basketball hoop with a net. It has a swish animation for something going through it. Its only code is to switch to the sBasket sprite when the sBasketSwoosh animation ends.

oRim

  • oRim is just the rim of the basketball hoop. This object has no code.

oRimDeflect

  • oRimDeflect is only the left and right edges of the rim of the basketball hoop. This object has no code.

Basketball#

SpelunkyClassic_Basketball_demo.gif The cast of Spelunky Classic will never be ballin'... Spelunky_SBasketball.png SpelunkyClassic_sDribble.gif

A basketball item exists: oBasketball. It can be picked up and thrown. When held over ground, the basketball uses a dribble animation. If the basketball is held in the air or held over nothing on the edge of a platform, it will use the normal sprite instead of being dribbled. When thrown, it bounces more than other items. The basketball defines bounceFactor as 0.7 and frictionFactor as 0.6, while other items like rocks use a bounceFactor of 0.5 and frictionFactor of 0.3. A thrown basketball cannot damage the player (only rocks and arrows can).

oBasketball has code for interacting with the basketball hoop series of objects, incrementing the Moon Room score, and executing code within the Moon Room's logic object.

  • Depending on how the basketball collides with oRimDeflect, it can get deflected away or bounce on the rim.
  • If the basketball falls through oRim, the basketball's code will make oBasket play the swish animation and one of three things can happen depending on how the basketball passes through oRim:
    • If the basketball is held and the player is in the FALLING state (16), a slam dunk will happen. One point is added to the Moon Room score and the player will drop the basketball.
    • If the basketball bounced off a wall and then went through the hoop, three points will be added to the Moon Room score.
    • If neither the above two happened, two points will be added to the Moon Room score.
  • When the basketball falls through oRim, it makes oMoonRoom (the logic object for the minigame) execute Alarm Event for alarm 0 after waiting 20 frames (2/3 of a second, Classic runs at 30 frames per second).

All of oBasketball's code can be seen here:

oBasketball's code from the Spelunky Classic source code:

Information about object: oBasketball

Sprite: sBasketball
Solid: true
Visible: true
Depth: 100
Persistent: false
Parent: oItem
Mask: <same as sprite>

Create Event:
call the inherited event of the parent object
execute code:

type = "Basketball";
makeActive()
setCollisionBounds(-4, -4, 4, 4)
bounceFactor = 0.7;
bounces = 0;
wallBounce = false;
frictionFactor = 0.6;
goingIn = false;

 Step Event:
call the inherited event of the parent object
execute code:

if (colLeft or colRight) wallBounce = true;
else if (colBot) wallBounce = false;

if (collision_rectangle(x-1, y+4, x+1, y+5, oRimDeflect, 0, 0) and not goingIn and bounces < 40)
{
    yVel = -yVel * 0.8;
    obj = instance_nearest(x, y, oRimDeflect);
    if (x < (obj.x+8) and abs((obj.x+8)-x) > 7)
    {
        xVel = -2;
        yVel = -0.5;
    }
    else if (x > (obj.x+8) and abs((obj.x+8)-x) > 7)
    {
        xVel = 2;
        yVel = -0.5;
    }
    else if (abs((obj.x+8)-x) < 4)
    {
        if (x < (obj.x+8)) xVel = 1;
        else xVel = -0.2;
        yVel = -0.5;
    }
    bounces += 10;
}

if (bounces > 0) bounces -= 1;

if (collision_point(x, y, oRim, 0, 0))
{
    if (not goingIn and (yVel > 0 or (held and oPlayer1.state == 16)))
    {
        with oMoonRoom { alarm[0] = 20; }
        xVel = 0;
        yVel = 0;
        goingIn = true;
        obj = instance_nearest(x, y, oBasket);
        obj.sprite_index = sBasketSwoosh;
        obj.image_index = 0;
        if (held)
        {
            oMoonRoom.baskets += 1;
            oPlayer1.holdItem = false;
        }
        else if (wallBounce)
        {
            oMoonRoom.baskets += 3;
        }
        else
            oMoonRoom.baskets += 2;
        held = false;
    }
}
else goingIn = false;

if (held and collision_point(x, y+6, oSolid, 0, 0)) sprite_index = sDribble;
else sprite_index = sBasketball;

Target#

A target object exists: oTarget. After the basketball challenge was scrapped, the Moon Room became an archery challenge which used this target object. This object was used until Spelunky Classic version 1.1. Version 1.1 revamped the Moon Room to use bat targets instead of this unique target object. Interestingly, the HUD icon for the Moon Room score appears to be based on this target as it uses the same colors in the same order.

When the target appears, it starts by moving up or down at random. It then continues to move up and down along the right side of the room, reversing direction before it hits the floor or ceiling. When the Moon Room timer reaches 29 seconds, in addition to moving up and down, the target will drift toward the center of the room, then drift back toward the right wall and repeat.

Hitting different parts of the target with arrows gives different amounts of points and will play a sound. Hitting the target will play hit.wav, and hitting the yellow part will additionally play coin.wav (the sound for collecting gold bars and similar treasure).

Color

Points

White

1

Black

2

Red

3

Yellow

5

Code in the Create Event and a comment within oTarget suggests that hitting it when its x position was lower then 208 (close to the middle of the room) would give double points, but this was scrapped.

All of oTarget's code can be seen here:

oTarget's code from the Spelunky Classic source code:

Information about object: oTarget

Sprite: sTarget
Solid: false
Visible: true
Depth: 0
Persistent: false
Parent: <no parent>
Mask: <same as sprite>

Create Event:
execute code:

life = 20;
dying = false;
UP = 0;
DOWN = 1;
dir = rand(0,1);
moveOff = 32;
hard = false;
if (x > 208) hard = true;
xDiff = 0;

 Step Event:
execute code:

if (dying)
{
    if (life > 0) life -= 1;
    else
    {
        life = 20;
        dying = false;
    }
}
else
{
    if (dir == UP)
    {
        if (hard) y -= 2;
        else y -= 1;
        moveOff += 1;
        if (y <= 64 or moveOff > 64)
        {
            dir = DOWN;
            moveOff = 0;
        }
    }
    else if (dir == DOWN)
    {
        if (hard) y += 2;
        else y += 1;
        moveOff += 1;
        if (y >= 160 or moveOff > 64)
        {
            dir = UP;
            moveOff = 0;
        }
    }
    
    if (oMoonRoom.timer <= 30)
    {
        x = 240 - abs(sin(xDiff)*64);
        xDiff += 0.01;
    }
}

Collision Event with object oArrow:
execute code:

other.xVel = 0;
other.yVel = 0;
if (not dying)
{
    dying = true;
    n = 0;
    if (other.y >= y-2 and other.y <= y+2)
    {
        n = 5;
        playSound(global.sndCoin);
    }
    else if (other.y >= y-6 and other.y <= y-3) n = 3;
    else if (other.y >= y+3 and other.y <= y+6) n = 3;
    else if (other.y >= y-10 and other.y <= y-7) n = 2;
    else if (other.y >= y+7 and other.y <= y+10) n = 2;
    else n = 1;
    //if (hard) n *= 2;
    oMoonRoom.baskets += n;
    playSound(global.sndHit);
}

if (life <= 2) with other { instance_destroy(); }

Commented-Out Source Code#

In the source code, oMoonRoom uses Alarm 0 to execute Alarm 11, which then handles counting down the timer and ending the minigame. However, commented-out code also exists in Alarm 0 for spawning the three basketball hoop objects with poof particle effects. The x coordinate for spawning these objects is defined as the variable n, but oMoonRoom does not use this variable. If this code is un-commented, the objects are spawned in the floor of the Moon Room, under the entrance. This can be seen in the image to the right.

Every time the basketball object passes through oRim, the basketball's code will execute oMoonRoom's Alarm 0. Un-commenting the code and having the basketball pass thorough oRim will spawn another set of basketball hoop objects in the same spot. Because another set of basketball hoop objects spawn, the original basketball challenge code may have used the n variable to spawn the hoop objects at a random x position in the challenge. It is also likely that scoring from a hoop would delete it and spawn a new hoop somewhere else.

Notes#

  • oBasket, oRim, oRimDeflect, and oTarget objects can be found in Objects\Other\Title. Notably, oBatTarget, the target used for the final Moon Room, is located in this folder as well.
  • oBasketball object can be found in Objects\Items.
  • The basketball hoop graphics can be found in Sprites\Other in sBasket, sBasketSwoosh, sRim, and sRimDeflect.
  • The basketball's graphics can be found in Sprites\Items\Weapons in sBasketball and sDribble.
  • The target graphic can be found in Sprites\Items\Weapons\sTarget.

Unused Power-Ups#

Jordans#

SpelunkyClassic_Jordans_jumping.gif Spelunky_SJordans.png

Object name: oJordans
An unused power-up very likely based on the Air Jordan line of basketball shoes. The Jordans are an even stronger version of Spring Shoes that allows six block high jumps, with enough height to be able to ledge grab a seventh block. Picking up oJordans gives the power-up and displays unique text.

      YOU GOT JORDANS!
YOU FEEL LIGHT ON YOUR FEET.

The Jordans also have unique text for being sold in a shop for the cost of $50,000. Unlike every other shop string in the game, the Jordans shop string does not use dynamic text for the value. All other shop strings use dynamic text for the progressively increasing values throughout the game.

JORDANS FOR $50000!

The Jordans' effects override the effects of Spring Shoes. Having both at once does not allow for even higher jumps. The Jordans do not have a HUD graphic.

Entering the Scores room runs code that sets hasJordans to false. Exiting the Tutorial to the Title Screen removes the Jordans twice from it running the scrClearGlobals script and then manually setting hasJordans to false.

Because of the real-life relation between the Air Jordan and basketball, the Jordans were likely meant for the Moon Room when it was still a basketball challenge. Removing the Jordans when entering the Scores room is likely a leftover of that to prevent the Jordans from persisting into the main adventure. The Jordans having shop text also suggests that it was meant to appear in the main adventure, but it ended up unused.

The pickup text can be found in Scripts\Character\scrStealItem. The object (and shop text) can be found in Objects\Collectables\oJordans. The graphic can be found in Sprites\Items\Saleable\sJordans.

Ninja Suit#

All that remains of this power-up is a global variable, hasNinjaSuit, and an unused graphic. The unused graphic was likely meant for the object that gives the power-up when picked up, as the graphic name matches the format of other power-up items. The power-up does nothing if it is enabled. The graphic can be found in Sprites\Items\Saleable\sNinjaSuit.

Unused Objects#

In addition to the basketball hoop, basketball, target, and Jordans discussed above, there are four more unused objects.

Gold embeds#

Object name: oGold
SpelunkyClassic_sGoldBig.png

Object name: oGoldBig
Unused objects of isolated versions of the gold veins found in tiles. These are unused because the game instead has the gold vein as part of the tile graphics, instead of overlaying a separate object on the tile like gems and items.

When spawned in the game, both objects are not solid and cannot be interacted with. The objects do have code to execute when destroyed, which spawns collectable gold nugget treasure. At a glance, the code appears to be the same as the code used by tiles to spawn gold nugget treasure, but there are many small differences:

  • The unused objects spawn the gold nuggets on the origin of the sprite, which is the top-left. The used code spawns the gold nuggets on the center of the block.
  • Part of the spawning of the gold nuggets randomly offsets where it spawns.
    • The unused objects maximum random offset is -2 to 2 units off the spawn point in both the X and Y axis.
    • The used code maximum random offset is -4 to 4 units off the spawn point in both the X and Y axis.
  • Part of the spawning of the gold nuggets applies a random Y velocity. Positive Y velocity is downward.
    • The unused objects spawn the gold nuggets with a Y velocity of 3 to 6.
    • The used code spawns the gold nuggets with a Y velocity of 2 to 4 (and then multiplies this number by 1 for some reason).
  • A different amount of treasure is potentially spawned by oGoldBig.
    • The unused oGoldBig randomly spawns 2 or 3 small gold nuggets, and always spawns a large gold nugget.
    • The used big gold vein code always spawns 3 small gold nuggets, and always spawns a large gold nugget.
    • The unused oGold and used small vein code always spawn 3 small gold nuggets.

The objects can be found in Objects\Blocks in oGold and oGoldBig. The graphics can be found in Sprites\Blocks\Gold in sGold and sGoldBig.

The concept of the gold embeds being separate objects attached to floor tiles would later be used in Spelunky HD and Spelunky 2.

Small Collect effect#

$$$ SpelunkyClassic_oSmallCollect_rising.gif

Object name: oSmallCollect
An unused particle effect object. The object is a slowly rotating dollar sign that slowly moves up and disappears after a second. The graphics the object uses is used by the game above on-sale shop items. It is similar to the large dollar sign particle that appears when an Idol is converted into score.

In the player object's code, with the code responsible for collecting treasure, commented-out code exists that would have spawned this object every time treasure was collected. This was probably scrapped because the effect is not needed for every treasure and it would have gotten annoying. The gif to the right shows what uncommenting the code and then collecting treasure looks like.

The object can be found in Objects\Effects\oSmallCollect. The graphic can be found in Sprites\Effects\sSmallCollect.

Thwomp Trap#

Various properties of the Thwomp Trap in action. Spelunky_SThwompTrap.png

Object name: oThwompTrap
An unused trap, who's behavior and name is likely based on the Thwomp enemies from the Super Mario series. The Thwomp Trap will float and do nothing until the player moves into the six tiles directly beneath it. This detection area can see the player through solid tiles. After detecting the player, it will quickly fall down and land. After landing, it will wait on the floor for 3 and 1/3rd seconds (100 frames), then slowly move up until it touches a ceiling. Once it hits a ceiling, its behavior will reset and it can fall again if it senses the player. The trap does not remember its initial position after falling, so when it moves back up, it can move past its initial position if there is no ceiling to stop it.

A few aspects of the Thwomp Trap are incomplete:

  • The trap cannot damage the player by landing on them or moving them into a ceiling. If this happens, the trap will stop moving until the player moves out of the way. It can crush enemies, however.
  • The detection area is about a block wide, but it is located on the left of the Thwomp Trap. This means that standing under the right half will not activate the trap, while approaching from the left will make it drop before the player is under it.
  • Ledge grabbing a moving Thwomp Trap will not carry the player with it.

It is not indestructible; it can be destroyed like any other normal tile.

The script scrRoomGen5, which generates the final boss level (level 16), defines the tile code t to spawn the Thwomp Trap. No level data there uses t, so it never spawns. What likely happened is that the tile code was defined in scrRoomGen4, which generates Temple levels, then it was copy-pasted into scrRoomGen5 and was forgotten, then it was removed from scrRoomGen4 once the trap was scrapped. Because of this, it was likely to appear in Temple.

The object can be found in Objects\Traps\oThwompTrap. The graphic can be found in Sprites\Traps\sThwompTrap.

Unused Graphics#

Playable Characters#

Throw Animation#

Guy Spelunky has a throw animation, which is never used. Throwing does not play an animation at all. Damsel and Tunnel Man do not have sprites for a throw animation. The gif of the throw animation is a mockup using the speed of the whip animation. These graphics can be found in Sprites\Character\Main Dude\sThrowLeft.

Sheet

Animation

sThrowLeft

This version of Guy Spelunky would reappear almost 12 years later in Spelunky 2 as a playable character with a throw animation, but his throw animation there uses different sprites.

Blocks#

Floor Decorations#

To do:
In Scripts\Level Generation\scrSetupWalls, commented-out code can be found to spawn objects not present in the final game that seem to be the floor decorations below.

Every area has unused graphics for decorations to appear on the sides of tiles, a concept that would later be used for Spelunky HD and Spelunky 2.

Mines

The only area with BottomL and BottomR graphics. These graphics can be found in Sprites\Blocks\Mines.

Lush

Although Lush has hanging vine decorations from the bottom of tiles, sLushBottom2 is a unique graphic that does not match the style of the used decorations. These graphics can be found in Sprites\Blocks\Lush.

Ice Caves

The "Dark" in the filenames is likely because the Ice Caves tileset is a darkened version of the Mines tileset that was made for the nighttime desert intro. The darkened tileset then went on to be used in Ice Caves without the tileset getting renamed. sIceBottom was likely going to be used for ice tiles. These graphics can be found in Sprites\Blocks\Dark.

Temple

City of Gold does not have equivalent graphics. These graphics can be found in Sprites\Blocks\Temple.

Metal Block#

A graphic that looks like a push block with a metal appearance. This graphic can be found in Sprites\Blocks\sMetalBlock.

Metal Door#

A graphic of a metal door with a yellow lock. Interestingly, the yellow lock on this graphic is very similar to the lock on the Locked Chest containing the Udjat Eye. This graphic can be found in Sprites\Blocks\sMetalDoor.

Kali Altar#

A graphic of one of Kali's sacrificial altars, but not covered in blood. This graphic can be found in Sprites\Blocks\Altars, as the second image of the sSacAltar sprite.

Grave#

Graves found in the dead are restless level feeling have six total sprites (not counting the Ash grave), but the Create Event code for oGrave randomly sets it to sGrave2, sGrave3, sGrave4, sGrave5, or sGrave6. This means that the graphic sGrave goes unused. sGrave4 uses a darkened version of sGrave's graphic. sGrave is used by the game as the initial graphic of oGrave before its Create Event, but it cannot be seen in the game. This graphic can be found in Sprites\Blocks\Graves\sGrave.

Blank Shop Sign#

A graphic of a blank shop sign. This graphic can be found in Sprites\Blocks\Shop\sSign.

Ice Caves Platform#

A graphic that looks like the tiles with a wooden flat top used for shops, but without the floor tile behind it. This graphic can be found in Sprites\Blocks\Dark\sDarkPlat.

Frozen Caveman Tile#

A graphic of an ice tile with a Caveman frozen inside it. This goes unused because the game instead overlays a separate frozen Caveman graphic on the ice tile, which allows the ice tileset to properly blend with other tiles. This graphic can be found in Sprites\Blocks\Dark\sIceCaveman.

Enemies#

Caveman#

Cavemen have separate graphics for worshipping right and worshipping left. Cavemen only worship right in the Olmec cutscene. Strangely, the unused worship left animation has less frames then the worship right animation, and some frames are different.

The worship left graphics have been horizontally flipped to make comparing then to the worship right graphics easier. The gif of the worship left animation is a mockup using the animation speed of the worship right animation. Because worship left has less frames, it is faster then worship right. The used graphics can be found in Sprites\Enemies\Caveman\sCavemanWorshipR, while the unused graphics can be found in Sprites\Enemies\Caveman\sCaveManWorshipL.

The worship left animation is missing the second and ninth frames of the worship right animation where the Caveman's arms are diagonal. The worship left sprites for the second and seventh frames do not match any of the used frames. Comparing the worship left fourth and fifth frames to the worship right's fifth and sixth frames, the black pixels representing the Caveman's closed eyes are different.

Sheet

Animation

sCavemanWorshipR
(Used)

sCaveManWorshipL
(Unused)
(horizontal flip)

Frog#

Frogs have graphics for what appears to be a full set of early sprites. Interestingly, the unused frog sprites are in the Sprites\Enemies folder under the names sprite624 and sprite625, which does not match the sprite name style used by the rest of the game. For context, the frog's used sprites are named sFrogLeft and sFrogJumpL. The gif of sprite624 is a mockup using the animation speed of the frog's idle animation.

The frog in the unused sprites is taller compared to the used sprites, which makes the unused frog have a bigger appearance.

Frog
(Used)

sprite624
(Unused)

Frog
(Used)

sprite625
(Unused)

Mantrap#

Mantraps have graphics for eating something with nothing there to be eaten. The filename is sManTrapTemp, which suggests that it was used as a template for the other eat animations. The gif of the animation is a mockup using the speed of the used eat animations. These graphics can be found in Sprites\Enemies\ManTrap\sManTrapTemp.

Sight#

The way that Cavemen and Hawkmen detect the player is by using an invisible object called oEnemySight. This object has a unique graphic that cannot be seen because it is always invisible. This graphic can be found in Sprites\Enemies\sSight.

Items#

Death Mask#

Spelunky_SDeathMaskPickup.png Spelunky_SDeathMaskWear.png

These graphics seem to be for a power-up that never got implemented. The large sprite seems like it would have been for the item pickup and HUD icon, while the small sprite would have been worn and appear on the spelunker. The large graphic can be found in Sprites\HUD\sDeathMask and Sprites\Items\Saleable\sDeathMaskPickup. The small graphic can be found in Sprites\Items\Saleable\sDeathMaskWear.

Egg#

A graphic of an...egg. Because it is found in Sprites\Items\Weapons\sEgg, it was likely to be an item that could be picked up and thrown.

Gold Egg#

A graphic of a gold egg. Because it is found in Sprites\Items\Treasures\sGoldEgg, it was likely to increase score when collected.

Bell#

A graphic of a yellow bell. Because it is found in Sprites\Items\Treasures\sBell, it was likely to increase score when collected.

Opened Crate#

Crates have an opened graphic. Opening crates in the game will cause the crate to disappear and only leave behind the item inside. Because chests leave behind an opened chest, it is likely that the same was going to happen for crates with this graphic. It is also possible that crates left behind an opened crate in development, but was scrapped because having the opened crate and the item inside on the same spot could cause confusion over what item will be picked up. This graphic can be found in Sprites\Items\Other\sCrateOpen.

Opened Locked Chest#

The Locked Chest containing the Udjat Eye has an opened graphic. Opening the Locked Chest runs code that sets the object to this sprite, but the code then deletes the object in the same function, making this graphic impossible to see. This graphic can be found in Sprites\Items\Other\sLockedChestOpen.

Code from oLockedChest's Collision Event with object oKey in the Spelunky Classic source code:

if (other.held and sprite_index == sLockedChest)
{
    other.held = false;
    with oPlayer1
    {
        holdItem = 0;
        pickupItemType = "";
    }
    with other { instance_destroy(); }
    
    sprite_index = sLockedChestOpen;
    playSound(global.sndChestOpen);
    obj = instance_create(x, y, oUdjatEye);
    obj.xVel = rand(0,3) - rand(0,3);
    obj.yVel = -2;
    obj = instance_create(x, y, oPoof);
    with obj { xVel = -0.4; }
    obj = instance_create(x, y, oPoof);
    with obj { xVel = 0.4; }
    instance_destroy();
}

Miscellaneous#

Small Collect effect#

Used

Unused

The small dollar sign seen rotating above on-sale items in shops actually has a full rotation animation, but only 10/20 frames are used. Some of the unused frames are visible in the unused oSmallCollect object, which uses the same graphics. These graphics can be found in Sprites\Effects\sSmallCollect.

Mushrooms#

Eight unused mushrooms, each one with a unique design, all located in their own folder: Sprites\Mushrooms. According to a Reddit AMA with Derek Yu, the mushrooms were originally intended to act like the potions in NetHack.

(Source: u/mossmouth on Reddit)

Lvl#

A graphic displaying the text "Lv.". It was likely a shortening of level, but the game always uses the full word whenever it is said. This graphic can be found in Sprites\sLvl.

Room#

...Yep

Note: a gray background has been added for visibility.

A graphic of a 10x8 grid of red squares with the top-left square being white. This graphic is used for the invisible oRoom object. If the source code is viewed, the object is shown in place of the randomly generated room templates in the rLevel, rLevel2, rLevel3, and rOlmec files in the Rooms folder. The script scrInitLevel runs with oRoom to run the level generation scripts on every instance of this object to create the level. This graphic can be found in Sprites\sRoom.

The screenshot to the right shows what making oRoom visible looks like.

Cloud Background#

This unused graphic, sprite1112, is a copy of the background used in the "YOU MADE IT!" screen and the daytime credits after it. The one difference is that the unused graphic is 40 pixels taller at the top compared to the used graphic. What likely happened is that this graphic was made and used, but most of the beautiful cloud pixel art was obscured by the desert landscape. To solve this, the top 40 pixels were trimmed to move the graphic up. This lets more of the background be seen, but the bottom half of the clouds is still obscured. It is also likely that this graphic was used as the base for the scene in the ending of the volcano. The used cloud background can be found in Backgrounds\bgClouds. The unused cloud background can be found in Sprites\Other\End\sprite1112.

Unknown Graphic#

This is a background graphic. It is located in a sheet that also contains copies of the closed unlockable room doors (Sun, Moon, Stars, and Changing Rooms). Because of this, it may have acted like a door. This graphic can be found in Backgrounds\bgTitle.

Unused Sounds#

bombready#

An alarm-like sound. The name suggests that it was going to be related to bombs.

letsexplore#

A sound of a 12 second melody. Interestingly, before Spelunky was decided as the name, Explorer was used.5

pause#

This sound is named pause, but pausing in the game is completely silent.

Unused Text#

NOW I'M REALLY STEAMED!

Displayed if the scrShopkeeperAnger script is run without providing an argument value and the player has not killed a Shopkeeper in the run. The normal argument values display the text for walking out of a shop with an item, destroying part of the shop, lighting a bomb or bringing a lit bomb into a shop, and hurting a Damsel. The game always provides an argument value for the script, so this text is never displayed.


I SHOULDN'T BE SELLING THIS!

Displayed when an on-sale Udjat Eye, Hedjet, Sceptre, or Kapala is viewed. None of these items are found in shops, so the text is telling the truth.


NO MATTOCKS ALLOWED!
THIS MEANS YOU!

This text suggests that Tunnel Man was not going to be allowed into the Sun Room, Moon Room, and Stars Room with this text being displayed instead of the normal explanation text. In the source code, the check to display this text uses if (false) with (global.isTunnelMan) commented out after it.

This text can be found three times, one for each Secret Challenge, in the source code at oHighscores's Draw Event code. This object handles the logic for the Scores Room, and the Draw Event code controls displaying the text.

Code for displaying the Sun Room text from oHighscores's Draw Event in the Spelunky Classic source code:

if (oPlayer1.y < 48+16)
{
    draw_set_color(c_yellow);
    draw_text(168, 96, "SUN ROOM");
    draw_set_color(c_white);
    if (tMoney >= sunGold)
    {
        if (false) // (global.isTunnelMan)
        {
            draw_text(128, 112, "NO MATTOCKS ALLOWED!");
            draw_text(128, 128, "THIS MEANS YOU!");
            draw_text(128, 144, "");
            draw_text(128, 160, "");
        }
        else
        {
            draw_text(128, 112, "KEEP YOURSELF AND");
            draw_text(128, 128, "THE DAMSEL ALIVE");
            draw_text(128, 144, "FOR AS LONG AS");
            draw_text(128, 160, "POSSIBLE!");
        }
    }
    else
    {
        draw_text(128, 112, "LOCKED.");
    }
}

YOUR SPIDER SENSE TINGLES!

This text is displayed as the second line of the Climbing Gloves pick up text if the player is holding a Web Cannon. This is not possible to see because power-up items need to picked up like normal items instead of automatically being collected on contact. Because Climbing Gloves need to be picked up manually, this requires the player to not be holding anything, including Web Cannons, which is the requirement for the text to be displayed.

This text can be found in the source code at Scripts\Player\scrStealItem.

Code for handling the player picking up Climbing Gloves from Scripts\Player\scrStealItem in the Spelunky Classic source code:

else if (holdItem.type == "Gloves")
{
    global.hasGloves = true;
    disp = instance_create(holdItem.x, holdItem.y-14, oItemsGet);
    with holdItem { instance_destroy(); }
    playSound(global.sndPickup);
    holdItem = 0;
    global.message = "YOU GOT CLIMBING GLOVES!";
    if (oPlayer1.pickupItemType == "Web Cannon") global.message2 = "YOUR SPIDER SENSE TINGLES!";
    else global.message2 = "";
    global.messageTimer = 120;
}

Unobtainable stats.txt Entries#

Spelunky Classic creates and uses a file called stats.txt to keep track of extended statistics. Due to code oversights, five entries are impossible to obtain.

Group

Entry
Number

Entry

Notes

ENEMY DEATHS

1

BAT

oBat Create Event code is missing (type == "Bat").

ENEMY DEATHS

7

ZOMBIE

oZombie Create Event code is missing (type == "Zombie").

ENEMY DEATHS

8

VAMPIRE

oVampire code for colliding with oCharacter overrides oEnemy's code for colliding with oCharacter.
oEnemy's code increments enemy deaths, but oVampire's code overrides this.

ENEMY DEATHS

12

MEGAMOUTH

oJaws Create Event code defines (type == "Mega Mouth"), while stat tracking expects (type == "Megamouth").

MISC DEATHS

6

BOULDER

No code exists to increment the killed by Boulder count.

stats.txt Oddities#

The following stats.txt entries are obtainable, the the code to increment the counter has some oddities.

Group

Entry
Number

Entry

Notes

MISC DEATHS

2

EXPLOSION

Requires getting hit by an explosion with more than 10 health, even if a death does not happen.
Explosions last for 10 frames (1/3 of a second, Classic runs at 30 frames per second) and deal 10 damage per frame.

MISC DEATHS

3

CRUSHED

Getting crushed in level 16 always counts as an Olmec death instead.

MISC DEATHS

7

ARROW TRAP

Dying to an arrow, even if it isn't from an Arrow Trap counts as an Arrow Trap death.

MISC DEATHS

8

SPEAR TRAP

Requires getting hit by the spears, even if a death does not happen.

MISC DEATHS

9

SMASH TRAP

Requires getting hit by the trap, even if a death does not happen.

MISC DEATHS

10

CEILING T.

Requires getting hit by the trap, even if a death does not happen.

Miscellaneous Content#

Damsel 12 Favor Sacrifice#

Sacrificing a stunned Damsel is supposed to give 12 favor by multiplying the Damsel's normal 8 favor by 1.5. This bonus favor is never given because the Damsel sacrifice code only runs in the THROWN (2) or DEAD (99) statuses. After the game checks to make sure that the Damsel is in one of these two statuses, it then checks if the Damsel is in status 98, which always fails because the code cannot run in status 98. Status 98 is used by other entities as the STUNNED state, but Damsels do not use this status. What makes this code oversight even more amazing is that Damsels are never assigned status 98 by anything, so even if the code let status 98 work for sacrificing, there is no way to set the Damsel's status to 98 anyway.

All of these oddities with the Damsel's sacrifice code suggests that the status 98 check is actually supposed to be a status 2 (THROWN) check, as that would give the bonus favor only if the Damsel is alive.

Code for handling the Damsel getting sacrificed from oDamsel's Step Event in the Spelunky Classic source code:

// sacrifice
if (status == THROWN or status == DEAD)
{
    if (not held and xVel == 0 and yVel == 0)
    {
        if (collision_point(x, y+8, oSacAltarLeft, 0, 0))
        {
            if (sacCount > 0) sacCount -= 1;
            else
            {
            instance_create(x, y, oFlame);
            playSound(global.sndSmallExplode);
            scrCreateBlood(x, y, 3);
            global.message = "KALI ACCEPTS YOUR SACRIFICE!";
            if (global.favor <= -8)
            {
                global.message = "KALI DEVOURS YOUR SACRIFICE!";
            }
            else if (global.favor < 0)
            {
                if (status == 98) global.favor += favor*1.5;
                else global.favor += favor;
                if (favor > 0) favor = 0;
            }
            else
            {
                if (status == 98) global.favor += favor*1.5;
                else global.favor += favor;
            }
            scrGetFavorMsg();
            global.messageTimer = 200;
            global.shake = 10;
            instance_destroy();
            }
        }
    }
    else sacCount = 20;
}

Unused Shop Prices#

Items which never appear in shops have defined costs.

Item

Value

Udjat Eye

0

Hedjet

999999

Sceptre

0

Kapala

999999

Jordans

50000

Bat Code Oversight#

Getting hit by an enemy checks if the enemy type is Bat, Piranha, or Vampire, and then spawns a blood object if the enemy is one of those defined types. Because the oBat Create Event code is missing (type == "Bat"), getting hit by a Bat does not spawn a blood object. This code can be found in the oEnemy Collision Event with object oCharacter code.

Lamp Value#

The item form of Lamps (not the ceiling version) defines a value of 1,000, which is used by treasure to determine how much score should be given. Lamps cannot be collected like treasure, so this value goes unseen.

Black Market Entrance Generation Failsafe#

The Black Market entrance spawn function has a failsafe if the entrance fails to generate: the failsafe is placing a Black Market entrance in every valid tile. This is caused by a missing break; in the function, so the code runs it on every valid tile. This failsafe should never happen because every failed attempt to place the Black Market entrance makes the next attempt more likely, until it is eventually a 100% chance to generate.

The City of Gold door spawn function uses the same failsafe, but it does use a break;, so it will not spawn a door on every tile. This failsafe also should not happen for the same reason as the Black Market entrance function: every failed attempt makes the next attempt to generate it more likely.

Ice Caves Scarab Spawn Chance#

Scarabs have a defined spawn chance of 1/40 per valid tile in dark Ice Caves levels, the same chance used for the rest of the game. Ice Caves levels can never generate dark, so this can never be seen.

Duplicate Level Feeling Texts#

The way that the level feeling text is implemented allows multiple level feeling texts to be displayed. There's three sets of level feeling texts, and some messages are duplicated into multiple sets. These duplicated messages are used if a level has multiple level feelings active. If a level feeling in the first set can happen at the same time as another set, the first set will cause the second set to be checked, and that can happen again for the third set. Strangely, the wet fur and psychic presence level feeling texts are in the first and second sets. The second set goes unused because Ice Caves levels never have two level feelings active at once. The Ice Caves level feelings being in the second set, and Ice Caves having a Scarab spawn chance suggests that the dark level feeling was allowed to happen in Ice Caves in development, but it was prevented because of the extreme difficulty darkness adds to Ice Caves.

Early Version Flare Leftovers#

scrClearGlobals defines global.flares as 3 with this comment in the source code after it: "not used any more". This flare count is a leftover from earlier versions of Spelunky Classic where flares were handled differently. Instead of flare crates found at the start of dark levels, the flare button had to be pressed to pull out a flare. Flares could not be retrieved once used, and there was no way to get more flares mid-run. The Spelunky Classic readme.txt still mentions the flare button, which is F with default keyboard controls. In the source code, checkFlarePressed is a leftover of the flare button, but its use has been commented out.

Oddities#

Inconsistent Graphics#

Block vs. Ending Platform#

The graphic of the sometimes pushable stone block is also used for the platform in the ending that gets launched by the lava. When compared, the ending platform version does not use the same graphic and has a smeared appearance.

sBlock

sEndPlat

Mantrap Animations#

The animation of a Mantrap eating something has the victim's sprites as part of the Mantrap's animation. Some of the Mantrap's sprites use frames of the victim that do not match, suggesting that sprites were updated, but the Mantrap's sprites did not get updated in the process.

The only difference between the two sprites here is a whopping two pixels. In the Mantrap image, a pixel on the top right of the hat is brown instead of dark brown, and a pixel near Guy's neck is orange instead of brown.

sDieLL

sManTrapEatL_3

The difference here is two pixels on the right side of the hat, and Guy's arm is curving down instead of going in a diagonal line.

sDieLBounce

sManTrapEatL_4

The only difference here is that the black outline above the Damsel's hair is missing in the Mantrap image.

sDamselDieLR

sManTrapEatDamselL_3

Rushing Water Alternate Text#

If a level has the rushing water level feeling active, the following message will be displayed:

I CAN HEAR RUSHING WATER...

However, if a level has the darkness, dead are restless, and rushing water level feelings all active at once, this different rushing water message will be displayed as the third message:

YOU HEAR RUSHING WATER...

This different message uses "YOU HEAR" instead of "I CAN HEAR".

Temple Shop Oversight#

Idols can be sold at shops to get the full value. This is done by setting the Idol on the oBrickSmooth floor object (the floor inside the shop) with a Shopkeeper alive and not angry. Because Temple shops (in levels 13, 14, and 15) do not use oBrickSmooth floor and instead use oTemple floor, it is impossible to sell Idols there.

Scarab Value Oddity#

Unlike other treasures which use a multiplier to increase the value in deeper worlds, the value of Scarabs is manually defined. The defined values causes an oddity: the value does not lineraly scale like every other treasure value in the game. Scarabs in the Mines are worth $4,000, Lush is worth $8,000, Ice Caves does not have a defined value, and Temple is worth $12,000. The scaling suggests that it is $4,000 times the area number, but Ice Caves not being accounted for caused Temple to have the wrong value. In the formula, Ice Caves Scarabs should be worth $12,000, which would cause Temple to be worth $16,000 instead.

Bomb Arrow Oddity#

When a Bow's arrow is fully drawn, the arrow can be converted into a bomb arrow by pressing the bomb button. Using a bomb arrow requires that the player has greater then zero bombs. Once a bomb arrow is used, there is not any code to subtract a bomb from the bomb count, so bomb arrows can be used without spending bombs.

Internal Names#

Main Dude#

The default playable character did not have an official name until future Spelunky games, but the folder containing his graphics gives an official name: Main Dude.

Lady Xoc#

The giant golden statue found in the City of Gold has the internal name Lady Xoc, which is never said anywhere in the game.

Area Names#

The areas of Spelunky Classic do not have official names said in the game, but song names and comments in the source code give official names. Strangely, some source code comments conflict with each other.

Levels

Song Name

Source Code Comment(s)

1 through 4

cave

"Room generation for Area 1, the Mines." -scrRoomGen

5 through 8

lush

"Room generation for Area 2, the Lush Jungle." -scrRoomGen2
"Room generation for the Black Market, which is accessible from Area 2: Lush." -scrRoomGenMarket

9 through 12

ice

"Room generation for Area 3, the Ice Caves." -scrRoomGen3
"Room generation for the Yeti Cave that appears randomly in Area 3: Ice." -scrRoomGenYeti

13 through 15

temple

"Room generation for Area 4, the Temple." -scrRoomGen4

16

boss

"Room generation for the final boss level (which is only partially randomized)." -scrRoomGen5

Secrets#

Tutorial Cat#

SpelunkyClassic_Tutorial_Cat.png SpelunkyClassic_bgHmm.png In the Tutorial, there is a hidden background graphic of a cat located under the entrance behind the terrain. It can be seen by progressing through normally until obtaining the Bomb Bag from the crate. Once the Bomb Bag is obtained, turn around and return to the entrance and use the bombs to destroy the tiles under it. It can also be seen by playing as Tunnel Man and using his Mattock to break the tiles instead of using bombs. The cat can be hard to see because it is using the same color pallet as the background, causing it to blend in. Interestingly, the name of the cat graphic is bgHmm.

The screenshot to the right shows the tiles removed and the cat visible. The second image under is the raw graphic.

References#


  1. Graphic at the bottom of the title screen.↩︎
  2. https://spelunkyworld.com/original.html↩︎
  3. https://web.archive.org/web/20150807114734/http://forums.tigsource.com/index.php?topic=4017↩︎
  4. http://www.mossmouth.com/spelunky2/#history↩︎
  5. EXPLORER.GMK: An Excerpt From the Spelunky Book gamedeveloper.com, March 23, 2016↩︎