====== Screen Transitions ======
{{..:data_locations:rosamus.png | MII Samus}} Screen transitions (sometimes referred to as "doors") in Metroid II use a bytecode interpreter to perform the various actions needed when moving between different rooms, such as changing the tileset, activating a fadeout, and so forth. A basic knowledge of the screen transition bytecode language and how to easily edit it are essential skills for making more elaborate hacks of this game.
It is recommended that you have a basic understanding of [[return_of_samus:technical_information:level_data_banks |Level Data Banks]] before continuing on.
===== Internal Workings =====
Each screen on each map has a screen transition index associated with it. As long as the index is not zero, the screen transition corresponding to the room will be activated once Samus touches any edge of the screen. (The pointer table indexed by the transition number starts at 0x142E5 in rom.)
Once the game identifies the script to use, it copies the next $40 bytes to ''$D700'' in RAM (which is the easiest place to inspect how they work). From there, it interprets the script byte-by-byte in a big loop. (Note that this means the //maximum// effective size of a door script is 64 (0x40) bytes, but you will probably never hit that limit.)
The interpreter first checks if it has reached the end of the script ($FF), and exits if that is the case. After that, it masks out the lower nybble of the current byte (e.g. $B2 becomes $B0), and then proceeds through a bunch of if-else statements to find the routine that matches the upper nybble (the "B" in $B2, for example), and then executes the command. After executing the command, the interpreter loops back to the beginning and repeats the process for the next operation.
(Note that all 16-bit values (pointers, etc.) are assumed to be little-endian, as usual.)
===== Tokens/Operators =====
Here is a list of valid tokens/operators used by the bytecode interpreter
==== FF - Terminating Operator ====
Self-explanatory.
==== 0* - VRAM/data Block Transfer ====
Copies a block of data starting at a specified bank and address to somewhere in RAM.
Syntax:
* ''01 bb ssss dddd llll'' - Saves bb:ssss as the source of a BG tileset (meaning the tiles will be loaded when reloading a save)
* ''02 bb ssss dddd llll'' - Saves ssss as the source of a sprite tileset (meaning the tiles will be loaded when reloading a save)
* ''0* bb ssss dddd llll'' - Just copies the data.
Operands:
* b - source bank
* s - source pointer
* d - destination pointer
* l - length (in bytes)
This doesn't seem to be used that often, probably because it's so verbose. The B* operator is typically used instead for loading sprite and background graphics.
==== 1x - Select metatile table ====
Copies the desired metatile table to $DA00-$DBFF in WRAM.
0 - finalLab
1 - ruinsInside
2 - plantBubbles
3 - queen
4 - caveFirst
5 - surface
6 - lavaCavesEmpty
7 - lavaCavesFull
8 - lavaCavesMid
9 - ruinsExt
The lava caves adjust their lava levels by changing their metatiles to sets with more or fewer lava tiles during door transitions.
The metatile table pointer list begins at ''0x23F1A'' in ROM. (For some reason, these pointers slightly out of order compared to how the data is stored in the rom.)
==== 2x - Select collision table ====
Copies the desired collision table to $DC00-$DCFF in WRAM.
Collision Tables
0 - plantBubbles
1 - ruinsInside
2 - queen
3 - caveFirst
4 - surface
5 - lavaCaves
6 - ruinsExt
7 - finalLab
The collision table pointer list begins at 0x23EEA in ROM.
==== 3x - Select solidity indexes ====
Copies the desired solidity indexes to WRAM, for Samus, enemies, and Samus' projectiles.
Basically, the way it works is that if an 8x8 tile is less than the specified index, then it is treated as a solid. While Samus, enemies, and projectiles may use different values, the process remains the same.
Solidity Values
0 - plantBubbles
1 - ruinsInside
2 - queen
3 - caveFirst
4 - surface
5 - lavaCaves
6 - ruinsExt
7 - finalLab
Note that (by default) these match up perfectly with the collision values previously.
The solidity indexes are stored in a table starting at 0x23EFA. Each entry row is four bytes wide, though only three are used.
The bytes are copied to these locations:
* $D056 & $D812 - Samus' solidity index
* $D056 & $D813 - Enemy solidity index
* $D08A & $D814 - Projectile solidity index
==== 4* - Warp ====
Warps Samus to the specified map and screen.
Syntax: ''4b yx''
Operands:
* b - map bank
* y - y-screen
* x - x-screen
Note: Place the x/y coordinate one screen "behind" the desired destination.
==== 5* - Retreat from Last Boss ====
This opcode is used only once, when Samus retreats from the last boss fight by falling through the hole in the floor. It performs several tasks:
- Disable LCD interrupt, ending special last boss hblank handling
- Move Samus to ($78, $D7)
- Set the screen position to ($80, $C0)
- Reload the status bar into the window after the last boss had used the window for the last boss head
- Flag the screen for a complete update
==== 6* - Change acid & spike damage ====
Syntax: ''6* xx yy''
Operands:
* xx - acid damage
* yy - spike damage
This is used towards the end of the game to increase the acid damage from $02 to $06.
For reference, the default starting values are $02 and $08. (Note that damage values for Samus in this game are binary-coded decimal.)
==== 7* - Exit Room After Last Boss Death ====
This opcode is used only once, when leaving the last boss room after defeating her. It performs the following:
- Disable last boss flag (sets $D08B to 0)
- Resets the window position to (0, $88) (sets $FF4A and $FF4B to $88 and 7, respectively)
- Disables LCD interrupt from last boss fight
- Reloads the status bar into the window
==== 8* - Transition to the Queen Fight ====
Handles a lot of special stuff regarding the queen fight, but not everything.
Form: ''8a bbbb cccc dddd eeee''
* a: Bank for current room ($F)
* b: Scroll Y position ($F48)
* c: Scroll X position ($EAE)
* d: Samus Y position ($F02)
* e: Samus X position ($EDE)
==== 9* - Conditional Operator ====
Syntax: ''9* nn xxxx''
Operands:
* nn - Number of Metroids
* xxxx - Transition index
"If the amount of Metroids remaining is less than or equal 'nn', then jump to and do transition 'xxxx' instead."
This is how the game handles lowering (and raising) acid levels. Keep in mind that earthquakes are handled elsewhere.
Note that this uses the real Metroid counter (memory address $D089), which also accounts for the Metroids in the endgame. Also keep in mind that this value is in binary coded decimal.
==== A* - Fadeout ====
When changing tileset, this is typically the first operator in the script.
==== B* - Load Graphics Page ====
Forms:
- ''B1 bb xxxx'' - Load BG graphics page (128 tiles) to $9000 in VRAM.
- ''B2 bb xxxx'' - Load Sprite graphics page (64 tiles) to $8B00 in VRAM.
Note that these opcodes update the variables that keep track of what tilesets to use when loading a save.
Operators:
* bb - source bank
* xxxx - source pointer
BG Graphics Pages
07:4000 plantBubbles
07:4800 ruinsInside
07:5000 queenBG
07:5800 caveFirst
07:6000 surfaceBG
07:6800 lavaCavesA
07:6D30 lavaCavesB
07:7260 lavaCavesC
08:71BC finalLab
08:79BC queenSPR
Sprite Graphics Pages
06:5920 enemiesA
06:5D20 enemiesB
06:6120 enemiesC
06:6520 enemiesD
06:6920 enemiesE
06:6D20 enemiesF
06:7120 arachnus
06:7520 surfaceSPR
08:59BC metAlpha
08:5DBC metGamma
08:61BC metZeta
08:65BC metOmega
08:69BC ruinsExt
08:71BC finalLab
08:79BC queenSPR*
* Note: queenSPR is loaded using the "0*" operator because it uses a nonstandard amount of tiles.
==== Cx - Change Music ====
0 - No change in music.
- Silences the roar from "song" A below
1 - "The Last Metroid"
2 - Queen Fight
3 - Inside Ruins
4 - Main Tunnels
5 - Ambience 1
6 - Ambience 2
7 - Ambience 3 (bugs)
8 - Omega Metroid Area
9 - Final Ruins
A - No music: SFX - Metroid Queen roar (persists)
B - Final Alarm
C - Metroid Fight
D - Ambience 4
E - SFX: Earthquake
F - Metroid Defeated
==== Dx - Load Message/Special Graphics ====
This loads the graphics for the orb, the desired item, the item font (well, up to the number 2), and the corresponding text string to VRAM.
0 - Save ... (just for the message)
1 - Plasma Beam
2 - Ice Beam
3 - Wave Beam
4 - Spazer
5 - Bombs
6 - Screw Attack
7 - Varia Suit
8 - High Jump Boots
9 - Space Jump
A - Spider Ball
B - Spring Ball
C - Energy Tank (unused)
D - Missile Tank (unused)
E - Energy (i.e. refill; again, unused)
F - Missiles (i.e. refill; unused)
Note that, for the last 4 items, their sprites are always in VRAM, and they don't cause text strings to appear, so this is unnecessary for them.
==== Invalid Operators ====
The game does not account for operators in the form of E* or F*. Using these will cause the game to freeze, because the interpreter will get stuck in an infinite loop. However, an intrepid hacker could use these to extend this language to do other radical things.
===== Shortcomings =====
Each screen can only have one transition assigned to it, meaning that if (for example) you need the left and right exits of a room to lead to different areas, then the room needs to be at least 2 screens wide (this limitation becomes very apparent when examining the original game's map design).
Also, the only conditional operator that exists is for the metroid count. Checking any other byte of the save data (such as if a particular enemy or item has been collected) is unsupported.
With the disassembly, however, both of these limitations could be overcome relatively trivially.
===== Usage =====
Transition scripts can be edited in one of two ways:
1) Using the transition editor in LAMP.
2) Editing [[https://github.com/alex-west/M2RoS/blob/main/SRC/maps/doors.asm|doors.asm]] in the disassembly, using the macros included in [[https://github.com/alex-west/M2RoS/blob/main/SRC/maps/door%20macros.asm|door_macros.asm]].
For reference, the macros the disassembly uses are as follows:
^ Opcode ^ Macro ^
| 00 bb ssss dddd llll | COPY_DATA |
| 01 bb ssss dddd llll | COPY_BG |
| 02 bb ssss dddd llll | COPY_SPR |
| 1x | TILETABLE |
| 2x | COLLISION |
| 3x | SOLIDITY |
| 4b yx | WARP |
| 50 | ESCAPE_QUEEN |
| 60 xx yy | DAMAGE |
| 70 | EXIT_QUEEN |
| 8a bbbb cccc dddd eeee | ENTER_QUEEN |
| 90 nn xxxx | IF_MET_LESS |
| A0 | FADEOUT |
| B1 bb xxxx | LOAD_BG |
| B1 bb xxxx | LOAD_SPR |
| Cx | SONG |
| Dx | ITEM |
| FF | END_DOOR |
==== Example ====
This the transition from the save room in the first ruins to the exterior area:
A0 B1 08 BC 69 26 36 19 B2 06 20 5D C5 49 E7 FF
Editing this by hand would by tedious and error-prone. However, with the disassembly it becomes fairly rudimentary:
door10C:
FADEOUT
LOAD_BG gfx_ruinsExt
COLLISION $6
SOLIDITY $6
TILETABLE $9
LOAD_SPR gfx_enemiesB
SONG $5
WARP $9, $E7
END_DOOR
Notice how this is largely human-readable, and how one can use the //label names// for the graphics locations in the ROM, rather than hardcoded offsets.